Motoren ohne delay per BT steuern

Hallo miteinander,

ich versuche per Bluetooth 3 Motoren und eine LED zu steuern.
Meine Hardware:
- Arduino Uno V3
- Adafruit Motor Shield V2
- Bluetooth Modul HC-06
- Motor 1,5V
- Taster
Ich habe folgende Scripte kombiniert
Using millis() for timing | Multi-tasking the Arduino - Part 1 | Adafruit Learning System
http://starthardware.org/lektion-12-der-taster-und-if-abfrage/
https://github.com/adafruit/Adafruit_Motor_Shield_V2_Library/blob/master/examples/DCMotorTest/DCMotorTest.ino
Mein Code
```
**/*
This is a test sketch for the Adafruit assembled Motor Shield for Arduino v2
It won’t work with v1.x motor shields! Only for the v2’s with built in PWM
control

For use with the Adafruit Motor Shield v2
----> http://www.adafruit.com/products/1438
*/

#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include “utility/Adafruit_MS_PWMServoDriver.h”

// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// Or, create it with a different I2C address (say for stacking)
// Adafruit_MotorShield AFMS = Adafruit_MotorShield(0x61);

// Select which ‘port’ M1, M2, M3 or M4. In this case, M1
Adafruit_DCMotor *myMotor = AFMS.getMotor(1);
Adafruit_DCMotor *myMotor3 = AFMS.getMotor(3);
Adafruit_DCMotor *myMotor4 = AFMS.getMotor(4);
// You can also make another motor on port M2
//Adafruit_DCMotor *myOtherMotor = AFMS.getMotor(2);

int tasterPin = 13;
int ledPin = 2;
// so now we have 3 motors called “motor”, “motor2” and “motor3”
// Variables will change:
int motorState = 0;           
int motorState2 = 0;           
int motorState3 = 0;           
int ledState = LOW; // ledState used to set the LED

long previousMillis = 0;        // will store last time motor1 was updated
long previousMillis2 = 0;        // will store last time motor3 was updated
long previousMillis3 = 0;        // will store last time motor4 was updated
long previousMillis4 = 0;    // will store last time LED was updated

//Intervall für ersten Motor
unsigned long intervalon = 0;          // interval at which to blink (milliseconds)
unsigned long intervaloff = 0;
       
//Intervall für zweiten Motor
unsigned long intervalon2 = 0;          // interval at which to blink (milliseconds)
unsigned long intervaloff2 = 0;
       
//Intervall für zweiten Motor
unsigned long intervalon3 = 0;          // interval at which to blink (milliseconds)
unsigned long intervaloff3 = 0;

//Intervall für Kamera
unsigned long intervalon4 = 0;          // interval at which to blink (milliseconds)
unsigned long intervaloff4 = 0;

const int NUMBER_OF_FIELDS = 9; // Wie viele kommaseparierte Felder erwarten wir?
int fieldIndex = 0;            // Das aktuell empfangene Feld
int values[NUMBER_OF_FIELDS];  // Array mit den Werte aller Felder
void readSerialString () {
  if( Serial.available()) {
    for(fieldIndex = 0; fieldIndex  < NUMBER_OF_FIELDS; fieldIndex ++)
    {
      values[fieldIndex] = Serial.parseInt(); // Numerischen Wert einlesen

}
    Serial.print( fieldIndex);
    Serial.println(" Felder empfangen:");
    for(int i=0; i <  fieldIndex; i++)
    {
      Serial.println(values[i]);
    }
    //fieldIndex = 0;  // und von vorn anfangen
  }
}

void setup() {
  Serial.begin(9600);          // set up Serial library at 9600 bps
  Serial.println(“Adafruit Motorshield v2 - DC Motor test!”);

AFMS.begin();  // create with the default frequency 1.6KHz
  //AFMS.begin(1000);  // OR with a different frequency, say 1KHz
 
  // Set the speed to start, from 0 (off) to 255 (max speed)
  myMotor->setSpeed(150);
  myMotor->run(FORWARD);
  // turn on motor
  myMotor->run(RELEASE);

myMotor3->setSpeed(150);
  myMotor3->run(FORWARD);
  // turn on motor
  myMotor3->run(RELEASE);

myMotor4->setSpeed(150);
  myMotor4->run(FORWARD);
  // turn on motor
  myMotor4->run(RELEASE);

pinMode(tasterPin,INPUT);
  pinMode(ledPin, OUTPUT);
}

void loop() {
readSerialString();
  if (values[0] ==1){
        //Intervall für ersten Motor
        long intervalon = values[1] * 1000;          // interval at which to blink (milliseconds)
        long intervaloff = values[2] * 1000;
       
        //Intervall für zweiten Motor
        long intervalon2 = values[3] * 1000;          // interval at which to blink (milliseconds)
        long intervaloff2 = values[4] * 1000;
       
        //Intervall für dritten Motor
        long intervalon3 = values[5] * 1000;          // interval at which to blink (milliseconds)
        long intervaloff3 = values[6] * 1000;

//Intervall für Kamera
        long intervalon4 = values[7] * 1000;          // interval at which to blink (milliseconds)
        long intervaloff4 = values[8] * 1000;       
       
  //Motor 1
  unsigned long currentMillis = millis();
  if((motorState == 1) && (currentMillis - previousMillis >= intervalon) && (digitalRead(tasterPin)==HIGH)) {
      previousMillis = currentMillis; 
      motorState = 0;
      myMotor->run(FORWARD);
      //digitalWrite(ledPin, ledState);
      Serial.println(“Motor1 is on”);
    }
    else if((motorState == 0) && (currentMillis - previousMillis >= intervaloff) || (digitalRead(tasterPin)==LOW)) {
      previousMillis = currentMillis; 
      motorState = 1;
      myMotor->run(RELEASE);
      //digitalWrite(ledPin, ledState);
      Serial.println(“Motor1 is off”);
    }

//Motor 3
    unsigned long currentMillis2 = millis();
    if((motorState2 == 1) && ( currentMillis2 - previousMillis2 >= intervalon2) && (digitalRead(tasterPin)==HIGH)) {
      previousMillis2 = currentMillis2; 
      motorState2 = 0;
      myMotor3->run(FORWARD);
      //digitalWrite(ledPin, ledState);
      Serial.println(“Motor3 is on”);
    }
    else if((motorState2 == 0) && (currentMillis2 - previousMillis2 >= intervaloff2) || (digitalRead(tasterPin)==LOW)){
      previousMillis2 = currentMillis2; 
      motorState2 = 1;
      myMotor3->run(RELEASE);
      //digitalWrite(ledPin, ledState);
      Serial.println(“Motor3 is off”);
    }

//Motor 4
    unsigned long currentMillis3 = millis();
    if((motorState3 == 1) && (currentMillis3 - previousMillis3 >= intervalon3) && (digitalRead(tasterPin)==HIGH)) {
      previousMillis3 = currentMillis3; 
      motorState3 = 0;
      myMotor4->run(FORWARD);
      //digitalWrite(ledPin, ledState);
      Serial.println(“Motor4 is on”);
    }
    else if((motorState3 == 0) &&  ( currentMillis3 - previousMillis3 >= intervaloff3) || (digitalRead(tasterPin)==LOW)){
      previousMillis3 = currentMillis3; 
      motorState3 = 1;
      myMotor4->run(RELEASE);
      //digitalWrite(ledPin, ledState);
      Serial.println(“Motor4 is off”);
    }

//LED
    unsigned long currentMillis4 = millis();
    if((ledState == HIGH) && ( currentMillis4 - previousMillis4 >= intervalon4) && (digitalRead(tasterPin)==HIGH)) {
      previousMillis4 = currentMillis4; 
      ledState = LOW;
      digitalWrite(ledPin, HIGH);
      Serial.println(“Camera is on”);
    }
    else if((ledState == LOW) &&  (currentMillis4 - previousMillis4 >= intervaloff4) || (digitalRead(tasterPin)==LOW)){
      previousMillis4 = currentMillis4; 
      ledState = HIGH;
      digitalWrite(ledPin, LOW);
      Serial.println(“Camera is off”);     
    } 
  }
}
__
```__
Per Bluetooth sende ich folgende Werte
__
> 1,5,5,5,5,5,5,5,5**__
Per Serial Print erhalte ich immer
> Motor1 is off
> Motor2 is off
> Motor3 is off
> Motor4 is off
> Camera is off
Was kann den falsch an der If-Abfrage sein?

if( Serial.available()) {
    for(fieldIndex = 0; fieldIndex  < NUMBER_OF_FIELDS; fieldIndex ++)
    {
      values[fieldIndex] = Serial.parseInt(); // Numerischen Wert einlesen

    }

Kontrolliert ob mindestens 1 Byte im Eingabe-Buffer der Seriellen Schnittstelle ist. Dann liest Du aber 9 Werte aus die nicht unbedingt vorhanden sein müssen.

Grüße Uwe

Vermutlich wartet parseInt() bis die benötigten Zeichen alle angekommen sind, und das ist meist unerwünscht. Der Puffer von Serial ist da ziemlich unkomfortabel, am besten alle verfügbaren Zeichen in ein char Array kopieren, und bei Eintreffen des Zeilenendes (CR und/oder LF, \r \n) die ganze Zeile parsen.

Du solltest Dich auch mal mit Arrays und Records bzw. Klassen (struct) befassen, um Deinen Code kürzer und übersichtlicher zu gestalten.

uwefed:

if( Serial.available()) {

for(fieldIndex = 0; fieldIndex  < NUMBER_OF_FIELDS; fieldIndex ++)
    {
      values[fieldIndex] = Serial.parseInt(); // Numerischen Wert einlesen

}



Kontrolliert ob mindestens 1 Byte im Eingabe-Buffer der Seriellen Schnittstelle ist. Dann liest Du aber 9 Werte aus die nicht unbedingt vorhanden sein müssen.

Grüße Uwe

Aber ich sende doch immer 9 Ziffern, welche durch ein Komma getrennt sind.

@DrDiettrich ich speichere doch die empfangenen Daten in einem Array
values[fieldIndex] = Serial.parseInt(); // Numerischen Wert einlesen
Oder sehe ich das falsch?

Die Klassen werde ich mir noch anschauen.

Die 9 Ziffern und 8 Kommas sind zusammen 17 Zeichen, vermutlich noch 1-2 für das Zeilenende. Du solltest also warten, bis so viele Zeichen angekommen sind:

if (Serial.available() >= 17) ...

Dann liegen mindestens 17 Zeichen im Puffer von Serial, und können mit parseInt() ausgelesen werden.

Mit const int NUMBER_OF_FIELDS = 9; gebe ich doch die Anzahl der kommaseparierte Felder an.
In meinem Beispiel sind es immer 9. auch wenn es zwei oder dreistellige Ziffern sind.

ich hatte den Empfgang per Bluetooth aus diesem Beispiel

// Receive multiple numeric fields using Arduino 1.0 Stream parsing

const int NUMBER_OF_FIELDS = 3; // how many comma-separated fields we expect
int fieldIndex = 0;             // the current field being received
int values[NUMBER_OF_FIELDS];   // array holding values for all the fields


void setup()
{
  Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud
}

void loop()
{

  if( Serial.available()) {
    for(fieldIndex = 0; fieldIndex  < 3; fieldIndex ++)
    {
      values[fieldIndex] = Serial.parseInt(); // get a numeric value

    }
    Serial.print( fieldIndex);
    Serial.println(" fields received:");
    for(int i=0; i <  fieldIndex; i++)
    {
       Serial.println(values[i]);
    }
    fieldIndex = 0;  // ready to start over
  }
}

Quelle: https://www.safaribooksonline.com/library/view/arduino-cookbook-2nd/9781449321185/ch04.html

parseInt() blockiert! Das wartet einfach solange bis alle Zeichen da sind. Standardmäßig bricht es erst ab wenn eine Sekunde lang nichts mehr empfangen wurde. Wenn du was ohne delay() willst, dann musst du darauf verzichten. Wie viele Standard Arduino Funktion ist das zwar einfach zu verwenden, aber für viele reale Anwendungen kaum zu gebrauchen.

Hier habe ich gezeigt wie man Zahlen nicht-blockierend einliest:

SERIAL_BUFFER_SIZE dabei so groß machen dass auch alles reinpasst. Und den Serial Monitor so einstellen dass er automatisch das Linefeed/newline am Ende sendet!

Alternativ die untere Version verwenden wo der String nicht erst gespeichert wird.

Die Auswertung ist etwas verbesserungsfähig. Man könnte z.B. auswerten wie viele Werte tatsächlich empfangen wurden, wenn es auch mal weniger als das Maximum sein können

hat man mit serial.read ebenfalls ein delay?

ich habe nun ein vereinfachtes scrippt das zunächst nur eine led blinken lässt.

/*
 * SerialReceiveMultipleFields sketch
 * This code expects a message in the format: 12,345,678
 * This code requires a newline character to indicate the end of the data
 * Set the serial monitor to send newline characters
 */

const int NUMBER_OF_FIELDS = 3; // how many comma separated fields we expect
int fieldIndex = 0;            // the current field being received
int values[NUMBER_OF_FIELDS];   // array holding values for all the fields

 
int ledPin =  13;      // the number of the LED pin
int ledState = LOW;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated
long OnTime = 0;           // milliseconds of on-time
long OffTime = 0;          // milliseconds of off-time


void setup()
{
  Serial.begin(9600); // Initialize serial port to send and receive at 9600 baud
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);       
}

void bluetooth() {
  if( Serial.available())
  {
    char ch = Serial.read();
    if(ch >= '0' && ch <= '9') // is this an ascii digit between 0 and 9?
    {
      // yes, accumulate the value if the fieldIndex is within range
      // additional fields are not stored
      if(fieldIndex < NUMBER_OF_FIELDS) {
        values[fieldIndex] = (values[fieldIndex] * 10) + (ch - '0'); 
      }
    }
    else if (ch == ',')  // comma is our separator, so move on to the next field
    {
        fieldIndex++;   // increment field index 
    }
    else
    {
      // any character not a digit or comma ends the acquisition of fields
      // in this example it's the newline character sent by the Serial Monitor
      
      // print each of the stored fields
      for(int i=0; i < min(NUMBER_OF_FIELDS, fieldIndex+1); i++)
      {
        Serial.println(values[i]);
        values[i] = 0; // set the values to zero, ready for the next message
      }
      fieldIndex = 0;  // ready to start over
    }
  }
}

void led (long onTime, long OffTime){
  // check to see if it's time to change the state of the LED
  unsigned long currentMillis = millis();
 
  if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
  {
    ledState = LOW;  // Turn it off
    previousMillis = currentMillis;  // Remember the time
    digitalWrite(ledPin, ledState);  // Update the actual LED
    Serial.println(onTime);
  }
  else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
  {
    ledState = HIGH;  // turn it on
    previousMillis = currentMillis;   // Remember the time
    digitalWrite(ledPin, ledState);    // Update the actual LED
    Serial.println(OffTime);
  }
}



void loop()
{
  bluetooth();
  led(values[0],values[1]);
}

handyaner:
Mit const int NUMBER_OF_FIELDS = 9; gebe ich doch die Anzahl der kommaseparierte Felder an.
In meinem Beispiel sind es immer 9. auch wenn es zwei oder dreistellige Ziffern sind.

Befasse Dich mal mit den Variablentypen.
Du sendest sagen wir mal um die 20-40 Zeichen und glaubst diese in 9 Variablen speichern zu können? Jede Ziffer und jedes Komma ist ein Zeichen das gesendet wird.
wenn Du zB
1,10,100 sendest, dann werden die Zahlen 49 44 49 48 44 49 48 48 seriell übertragen. Da gibt es keine Interpretation der Werte und es werden auch nicht 1 10 und 100 abgespeichert sondern wenn Du 3 Werte liest bekommst Du 49 44 und 49.

Du mußt den Eingangspuffer bei 9 Werten von bis zu 3 Stellen mit Komma getrennt und mit wahrscheinlich 2 Schlußzeichen mindestens 37 Byte lang haben.
Du brauchst keine INT variable sondern es genügt ein BYTE.

Uwe

hat man mit serial.read ebenfalls ein delay?

Nein, das liest das nächste Zeichen im Eingangspuffer. Und liefert -1 wenn nichts da ist (wobei das schon vorher mit available() abfängt).

Das mit den mehrstelligen Zahlen musst du natürlich extra behandeln

In der ersten Version in meinem Link werden die eingehenden Daten als C String behandelt und erst mal komplett gepuffert. Danach wird mit String Funktionen getrennt und konvertiert. Das ist sehr, sehr flexibel, aber braucht bei langen Strings auch recht viel Speicher (mindestens ein Byte pro Zeichen + 1).

Bei der zweiten Version werden die Ziffern direkt aufaddiert. Man multipliziert immer den aktuellen Wert mit 10 und addiert die aktuelle Ziffer dazu. Also z.B. 523:
5 -> 0 * 10 + 5 = 5
2 -> 5 * 10 + 2 = 52
3 -> 52 * 10 + 3 = 523

Für rein positive Zahlen sehr einfach, man verliert die Flexibilität von Strings. Die man aber auch nur braucht wenn man komplexere Sachen machen will. z.B. Variablen anhand von gesendetem Text oder Zeichen zu identifizieren.