Problema con delay utilizzando i2c

Salve a tutti,
riscontro un problema utilizzando il comando delay interno a loop() in un ardino nano.

Questo è il codice:

#include "Arduino.h"
// Include the Wire library for I2C
#include <Wire.h>
#include <L298NX2.h>

#define SLAVE_ADDRESS 0x08

// Pin definition
const unsigned int EN_A = 10;
const unsigned int IN1_A = 4;
const unsigned int IN2_A = 8;

const unsigned int IN1_B = 7;
const unsigned int IN2_B = 12;
const unsigned int EN_B = 11;
// Initialize both motors
L298NX2 motors(EN_A, IN1_A, IN2_A, EN_B, IN1_B, IN2_B);

volatile boolean receiveFlag = false;
char temp[32];
long time0;
int separate(
  String str,
  char **p,
  int size) {
  int n;
  char s[100];

  strcpy(s, str.c_str());

  *p++ = strtok(s, "_");
  for (n = 1; NULL != (*p++ = strtok(NULL, "_")); n++)
    if (size == n)
      break;

  return n;
}

void setup() {
  // initialize i2c as slave
  Wire.begin(SLAVE_ADDRESS);

  // define callbacks for i2c communication
  Wire.onReceive(receiveEvent);

  Serial.begin(9600);
  Serial.println("Ready!");
}

void loop() {

  if (receiveFlag == true) {
    String cmd = String(temp);
    Serial.print(F("CMD recieved is: "));
    Serial.print(cmd);
    Serial.print(F(" lunghezza comando "));
    int l = cmd.length();
    Serial.println(l);

    char *sPtr[SPTR_SIZE];
    int N = separate(cmd, sPtr, SPTR_SIZE);

void loop() {

  if (receiveFlag == true) {
    String cmd = String(temp);
    Serial.print(F("CMD recieved is: "));
    Serial.print(cmd);
    Serial.print(F(" lunghezza comando "));
    int l = cmd.length();
    Serial.println(l);

    char *sPtr[SPTR_SIZE];
    int N = separate(cmd, sPtr, SPTR_SIZE);

 if (strcmp(sPtr[0], "4") == 0) {
      Serial.println(F("Max Move to Right"));
      motors.setSpeedA(200);
      motors.setSpeedB(80);

      motors.backwardA();

      motors.backwardB();
      delay(100);
      motors.stop();
 } else if (strcmp(sPtr[0], "5") == 0) {
      Serial.println(F("Max move to Stop"));
      // Stop
      motors.stop();
    }
    receiveFlag = false;
  }    receiveFlag = false;
  }


void receiveEvent(int howMany) {

  for (int i = 0; i < howMany; i++) {
    temp[i] = Wire.read();
    temp[i + 1] = '\0'; 
  }

  for (int i = 0; i < howMany; ++i)
    temp[i] = temp[i + 1];

  receiveFlag = true;
}

Per qualche motivo qualsiasi valore io passo a delay(100) i motori girano continuamente.
Ho letto in qualche forum che potrebbe dipendere da interrupts di i2c.
Potrei utilizzare un'altra funzione con mills() per esempio o esiste una soluzione che attualmente non conosco?

Grazie per qualsiasi tipo di aiuto

Ma compila quella roba li ?

Il motivo lo devi cercare altrove ... delay() è una funzione che usa proprio millis() (ovvero legge semplicemente un contatore) che, su Arduino è sempre in funzione e, di sicuro, non va ad interferire con il bus I2C.

Guglielmo

Come minimo c'è una 'a' che se ne va a spasso... :slight_smile:

Ma i motori girano sempre o in alcune condizioni sono fermi?

Sinceramente... perché mettere di mezzo Sting quando già lavori e ricevi i dati in temp che è array di char?
Cioè hai già array di char, perché convertirlo in String e poi riconvertire String in array di char dentro la funzione separate?

P. S. ma nella receiveEvent il secondo for che cosa fa? Sembra mangia il primo char?

Sembra anche a me
Dopo che il primo ha 'sfondato' di uno...

Inoltre condivido tutte le considerazioni sul resto del codice...

Innanzitutto grazie a tutti.
Utilizzo string semplicemente perchè avevo riutilizzato una funzione di n altro programma.
Infatti una volta che funziona tutto, la string la elimino.

il loop corretto è il seguente:

void loop() {

  if (receiveFlag == true) {
    String cmd = String(temp);
    Serial.print(F("CMD recieved is: "));
    Serial.print(cmd);
    Serial.print(F(" lunghezza comando "));
    int l = cmd.length();
    Serial.println(l);

    char *sPtr[SPTR_SIZE];
    int N = separate(cmd, sPtr, SPTR_SIZE);

 if (strcmp(sPtr[0], "4") == 0) {
      Serial.println(F("Max Move to Right"));
      motors.setSpeedA(200);
      motors.setSpeedB(80);

      motors.backwardA();

      motors.backwardB();
      delay(100);
      motors.stop();
 } else if (strcmp(sPtr[0], "5") == 0) {
      Serial.println(F("Max move to Stop"));
      // Stop
      motors.stop();
    }
    receiveFlag = false;
  }

Il motore gira fin quando non riceve come input il valore 5.
Il delay per qualche motivo non viene rispettato.
Prima di fare il programma definitivo, stavo testando sia la libreria che i2c per questo il codice non è il massimo. Scusatemi.

Che cosa dovrebbe fare quel delay di 100 millisecondi?

Presumo lui mette i due motori in avanti, fa pausa di tot millisecondi (qui 100) e poi stoppa i motori

@robotty, ma hai provato un programma semplice con solo gestione dei motori ?
Una cosa del tipo di continuo, avanti 1 secondo, stop, avanti 1 secondo, etc. ?

void loop() 
{   motors.setSpeedA(200);
    motors.setSpeedB(200);
    motors.backwardA();
    motors.backwardB();
    delay(1000);
    motors.stop();
    delay(1000);
}

Se questo funziona, allora poi a questo aggiungi le varie librerie, NON gestisci i comandi (lasci questa loop) ma comunque metti la gestione wire (compresa receiveEvent) e setup che hai ora
Tanto per capire dove c'e' eventuale conflitto software (se c'e')

100 millisecondi?...

@datman, credo lui abbia provato vari valori. anche a me 100 mi pare poco da apprezzare, mi pare comunque lui da velocità diverse ai motori in quel pezzo x fare rotazione a dx. Però a lui vanno in continuo.

Ma siamo sicuri che i driver non siano guasti?
(anzi, il driver)

si potrebbero mettere una manciata di led sulle uscite per vedere...

questa riga non mi convince
se il delay non funzionasse i motori si fermerebbero subito (no delay) oppure il sistema non reagirebbe più (delay infinito)

in quale situazione siamo?
il sistema reagisce?
hai riscontro dei varii comandi ricevuti? la lunghezza dei comandi ricevuti corrisponde?

questa riga mi convince ancora meno
sembra indicare che il sistema è reattivo
ma allora al termine del delay (che termina di certo, altrimenti il sistema non reagirebbe) i motori vengono fermati da motors.stop()
quindi cosa li riavvia?
non è per caso che il trasmettitore trasmette di continuo il comando di start?

Innanzitutto grazie.
Delay 100 è un valore utilizzato come test.
Sono partito da valori molto più alti.
Utilizzando con uno sketch il modulo senza la libreria wire, funziona tutto regolarmente.
Il punto è che il delay non reagisce. Se andasse all'infinito in teoria non dovrebbe rientrare nella funzione loop come invece avviene.
Infatti se passo prima il valore 4, i motori iniziano a girare.
Inviando da i2c tramite python il valore 5 i motori si bloccano.
Il programma con cui invio il comando, è scritto in python. accetta in input il valore e lo trasmette.
Non viene inviato il valore continuamente.
Vorrei provare con la seriale senza i2c.

Cercando nei vari forum, ho trovato questo:
delay is not working in I2C program of arduino

In particolare questa risposta

I'm not sure what diddling with the TWCR register is supposed to accomplish, but it does not re-enable all interrupts, so timer events don't happen, and delay() doesn't work.

Potrebbe essere il mio caso?

Secondo me no. Abbiamo visto molti programmi con I2C e delay, ma visto problemi.
Piuttosto nel tuo programma:

  1. la void receiveEvent(int howMany) { dovrebbe essere "veloce" e con poco codice. Da I2C quanta roba spedisci ? Già chiesto ma non rispondi, a che diavolo serve secondo for (che rallenta quella funzione gestita in un interrupt) ?? Il comando che invii come è fatto ?

  2. nel loop() metti 2 volte receiveFlag = false;
    secondo la logica del tuo programma NON ha senso. Ha senso solo quello dentro l'if

  3. per toglierti il dubbio, prima del delay() prova a mettere comando interrupts(); (abilita interrupt)
    "interrupts() - Arduino Reference"

Ciao,
da i2c una volta che riesco a farlo funzionare dovrei inviare un dato simile al seguente:
1_2_4000
Il secondo for lo uso per rimuovere l'ultimo carattere.
Posterò anche il codice python.
Per il secondo punto elimino il secondo receiveflag.

Quello di utilizzare interrupts(), lo avevo visto ma volevo usarlo.

volevo fare una cosa simile:

  noInterrupts();
  if (strcmp(sPtr[0], "4") == 0) {
      Serial.println(F("Max Move to Right"));
      motors.setSpeedA(200);
      motors.setSpeedB(80);

      motors.backwardA();

      motors.backwardB();
      delay(100);
      motors.stop();
 } else if (strcmp(sPtr[0], "5") == 0) {
      Serial.println(F("Max move to Stop"));
      // Stop
      motors.stop();
    }
    receiveFlag = false;
  }
  interrupts();

No, intendo proprio prima del delay:

...
      motors.backwardA();
      motors.backwardB();
      interrupts();
      delay(100);

?? vuoi dire il primo a sx ?

for (int i = 0; i < howMany; i++) {
    temp[i] = Wire.read();
    temp[i + 1] = '\0'; 
  }
  for (int i = 0; i < howMany; ++i)   temp[i] = temp[i + 1];

Dopo il primo avrai in temp "1_2_4000" più carattere \0
Dopo il secondo avrai "_2_4000" più carattere \0 essendo in i metti da i+1

devo però riabilitare con nointerrupts() per riabilitare l'evento di i2c?
Giusto?

No, prova solo con interrupts(). L'idea è che qualche lib disabilita interrupts quindi noi li "riaccendiamo" prima di delay

Ok, in questi giorni provo ed aggiorno.