Comunicazione I2c tra arduino

Buongiorno a tutti.
Dunque,mi sto cimentando nell’utilizzo della comunicazione I2c tra un arduino uno e un arduino mega.Nonostante abbia cercato tante guide online non riesco a trovare esempi adatti al mio scopo…Perlomeno,ne ho trovato uno ma non riesco a farlo funzionare come vorrei…Sostanzialmente l’arduino mega è il master che a determinate operazioni invia allo slave(uno) un dato sotto forma di int credo…lo slave in ricezione lo apprende e lo converte in char…allo stesso tempo vorrei assegnarlo a una variabile generale che faccia mutare le funzioni di un if o switch…questo non mi riesce…ho provato anche a stampare il dato ricevuto e effettivamente lo vedo sul serial monitor…ma non riesco a capire perchè sotto forma di variabile comparatore in if non funziona…ecco lo sketch

arduino mega master

#include <Wire.h>

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

void loop(){
  Wire.beginTransmission(7);
  Wire.write("A");
  Wire.endTransmission();
 
}

questo sotto invece è lo slave

arduino uno slave
#include <Wire.h>

int ledPin = 9;

void setup(){
  Wire.begin(7);
  Wire.onReceive(ricevi);
  pinMode(ledPin, OUTPUT);
}

void loop(){
   if(comando == "A"){//ho provato anche a mettere il dato in 'A' ma non cambia
digitalWrite(ledPin, HIGH)
}
}


void ricevi(){
  String comando = "";
  while(Wire.available()){
    comando += char(Wire.read());
  }
}

Aggiungendo un serial println dopo il while di void ricevi visualizzo correttamente il carattere A ma non riesco a capire come convertirlo e associarlo a una variabile…ho provato anche creare una variabile generale ma mi da errore di conversione da una variabile string a char oppure char to char. :sob:

Grazie a tutti

Per quanto possibile, evita sempre la classe “String” … difatti NON sei su un PC dove c’è un sistema perativo ed un “garbage collector”, sei su una piccola MCU con solo 2KBytes di SRAM, dove devi fare tutto tu e dove usare la classe “String”, a causa dell’allocazione e riallocazione dinamica della memoria, porta quasi sempre … a grossi problemi e sicuri mal di testa !!! :smiling_imp:
Come ti è stato detto, impara ad usare le stringhe classiche del C … ovvero semplici array di char terminati dal carattere null (0x00) e le funzioni che trovi nella libreria standard (… che, oltretutto, è automaticamente inclusa dal IDE) AVR libc ed, in particolare, quanto è in <string.h> ;).
Se cerchi sul forum troverai una camionata di topic dove ne abbiamo parlato.

Wire.write('A'); //nota l'apice singolo, i doppi apici fanno una stringa (array di char), l'apice singolo è appunto un singolo carattere

Parte ricezione, non riesci a “vedere” la variabile comando perché l’hai definita locale alla funzione ricevi, definiscila globale ma non String ma bensì come array di char

char[10] comando;

la dimensione la devi determinare in base alla lunghezza del comando massimo che invierai più uno che è il carattere terminatore \0 che deve sempre essere presente nelle stringhe classiche del C
la parte di ricezione dovrai mettere i caratteri che ricevi dentro l’array di char incrementando sempre l’indice ad ogni carattere, quando hai finto di ricevere metti il terminatore nella posizione imemdiatamente successiva all’ultimo carattere ricevuto.
Poi per confrontare il comando ricevuto nel loop non potrai fare == ma dovrai usare l’apposita funzione strncmp

lo slave in ricezione lo apprende e lo converte in char…allo stesso tempo vorrei assegnarlo a una variabile generale che faccia mutare le funzioni di un if o switch…questo non mi riesce.

C’è lo sketch di esempio nell’ide “slave_receiver” sotto Wire.

// Wire Slave Receiver
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Receives data as an I2C/TWI slave device
// Refer to the "Wire Master Writer" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>

void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
}

void loop() {
  delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
    switch (c) {
      case 'A':
      break;
      case 'B':
      break;
      case 'C':
      break;
      
    } // end switch (c)
  }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

Ciao.

Maurotec:
C'è lo sketch di esempio nell'ide "slave_receiver" sotto Wire.

Intanto grazie per la risposta..ho provato con questo sketch che è come quello di esempio della libreria ma non funziona..aprendo anche il monitor seriale non vedo il dato ricevuto..Inoltre penso che(correggimi se sbaglio) per come è impostato il byte ricevuto lo acquisisce una volta sola..essendo la funzione racchiusa nel while di setup..a me serve che lo slave continui nel loop le sue funzioni ma che siano dinamiche rispetto a gli eventi che gli comunica l'altro arduino..per questo parlavo di creare una variabile globale.Non riesco a capire la soluzione... :frowning:

Il master deve spedire un char, così:

Wire.write('A');

Nota che ho usato gli apici singoli.
Che poi è uguale a spedire un intero di valore 65 seguendo la codifica ASCII.

per come è impostato il byte ricevuto lo acquisisce una volta sola..essendo la funzione racchiusa nel while di setup..a me serve che lo slave continui nel loop le sue funzioni ma che siano dinamiche rispetto a gli eventi che gli comunica l'altro arduino..per questo parlavo di creare una variabile globale.Non riesco a capire la soluzione... :frowning:

No e che la Wire è di tipo event driven, cioè guidata da eventi, per cui quando un dato viene ricevuto questo rappresenta un evento ed in tal caso la funzione void receiveEvent(int howMany) è il gestore degli eventi.

Se non si presenta alcun evento wire, il loop viene eseguito.

Diversamente da event driven (guidata da eventi), c'è la tecnica del polling, dove nel ciclo controlli costantemente se qualcosa di tuo interesse avviene. Cioè:

void loop()  {

  while (Wire.available()) {  
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
    switch (c) {
      case 'A':
      break;
      case 'B':
      break;
      case 'C':
      break;
     
    } // end switch (c)
   
  } // end while

} // end loop

Presumo tu voglia instaurare un dialogo fatto di botta e risposta?
Ciao.

Maurotec:
Il master deve spedire un char, così:

Wire.write('A');

esatto ho inserito proprio così..

No e che la Wire è di tipo event driven, cioè guidata da eventi ....

questa è una cosa interessantissima che non sapevo..ti ringrazio molto per la spiegazione ad hoc..mi informerò meglio su questo.. :smiley:

Presumo tu voglia instaurare un dialogo fatto di botta e risposta?
Ciao.

esatto proprio così..lo slave ipoteticamente esegue un on/off su un led dopo aver ricevuto dal master un comando e finchè non ne riceve un'altro che cambia l'esecuzione dello switch continua a fare on/off

>frami92: Quando si quota un post, NON è necessario riportarlo (inutilmente) tutto; bastano poche righe per far capire di cosa si parla ed a cosa ci si riferisce. Gli utenti da device "mobile" ringrazieranno per la cortesia :wink:

Guglielmo

P.S.: Ho troncato io il tuo quote qui sopra.

Scusami Guglielmo..mi dimentico di fare preview ogni volta :confused:

esatto proprio così..lo slave ipoteticamente esegue un on/off su un led dopo aver ricevuto dal master un comando e finchè non ne riceve un'altro che cambia l'esecuzione dello switch continua a fare on/off

Ok allora ti bastano due codici ASCII a scelta A e B.

quindi nello switch dello slave:

switch (c) {
      case 'A':
      // qui il comando per accendere il blink 
      break;
      case 'B':
      // qui il comando per spegnere il blink  
      break;

     
    } // end switch (c)

In ognuno dei CASE puoi anche inviare dati al master il quale dopo avere inviato il comando si mette in ascolto sul BUS.

Ciao.

Maurotec:
Diversamente da event driven (guidata da eventi), c'è la tecnica del polling, dove nel ciclo controlli costantemente se qualcosa di tuo interesse avviene. Cioè:

Ho usato l'esempio di spostare la lettura dei byte sul loop e funziona benissimo..mi dava un errore sul Wire.onReceive perchè se mettevo (); necessariamente aveva bisogno di richiamare una funzione come hai accennato te..quindi nelle parentesi ho messo loop e funziona..almeno nei vari case posso richiamare variabili globali che imposterò per eseguire altre funzioni.. :slight_smile: grazie ancora!!!

hai accennato te..quindi nelle parentesi ho messo loop e funziona..almeno nei vari case posso richiamare variabili globali che imposterò per eseguire altre funzioni.. :slight_smile: grazie ancora!!!

mmmm....mica funziona così.
La wire ha un buffer di dati grande 32 byte dove vengono conservati i dati ricevuti. Puoi controllare nel ciclo se ci sono o meno dati nel buffer con il metodo available() se c'è ne leggi con read se non c'è ne fai altro.
Questo approccio si chiama genericamente polling, che normalmente non è desiderato dal programmatore, che preferisce l'approccio event driven.

Se vuoi usare il polling non devi registrare alcuna funzione per gestire i dati in ingresso, quindi la riga:

Wire.onReceive(receiveEvent); // register event

la puoi commentare.

Sfruttando receiveEvent come gestore degli eventi il codice nel loop continua ad essere eseguito, a seguito di un evento (il dato che entra) viene interrotto il loop e si salta ad eseguire la funzione receiveEvent che una volta terminata restituisce il controllo al loop.

Comunque se posti il codice con cui stai sperimentando si può meglio comprendere e spiegare entrambe gli approcci.

Ciao.

Maurotec:
Comunque se posti il codice con cui stai sperimentando si può meglio comprendere e spiegare entrambe gli approcci.

Ciao.

In effetti era troppo presto per cantar vittoria..dopo aver creato lo sketch dello slave noto ritardi nell'eseguire le operazioni dello slave..stasera ti posto il codice così mi dai un tuo parere..grazie ancora per tutto il supporto :smiley:

Maurotec:
Comunque se posti il codice con cui stai sperimentando si può meglio comprendere e spiegare entrambe gli approcci.
Ciao.

ecco lo sketch dello slave…noto ritardi nel gestire le funzioni…il dato lo riceve perchè il ciclo for di luci si interrompe ma non riesce a eseguire il ciclo dell’audio…oppure lo fà con un notevole ritardo.Può essere che rimane in ascolto di dati? grazie

#include <Wtv020sd16p.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <FastLED.h>
#define LED_PIN 12
#define LED_PIN2 13
#define NUM_LEDS 22
CRGB leds[NUM_LEDS];

int resetPin = 2;  // The pin number of the reset pin 1.
int busyPin = 5;  // The pin number of the busy pin 15.

int clockPin = 3;  // The pin number of the clock pin 7.
int dataPin = 4;  // The pin number of the data pin 10.

Wtv020sd16p wtv020sd16p(resetPin, clockPin, dataPin, busyPin);

byte gLed = 0;
byte rLed = 0;
byte bLed = 0;
byte introLa = 0;
byte prePa = 0;
byte disinNa = 0;
byte introPa = 0;
byte missCa = 0;

void setup() {
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.addLeds<WS2812, LED_PIN2, GRB>(leds, NUM_LEDS);
  wtv020sd16p.reset();
  delay(1000);
  wtv020sd16p.setVolume(7);
  Wire.begin(7);
  Wire.onReceive(loop);
  Serial.begin(9600);
}

void loop() {
  while (Wire.available()) {
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
    switch (c) {
      case 'A':
        gLed = 1;
        rLed = 0;
        bLed = 0;
        break;
      case 'B':
        gLed = 0;
        rLed = 1;
        bLed = 0;
        break;
      case 'C':
        gLed = 0;
        rLed = 0;
        bLed = 1;
        break;
      case 'D':
        gLed = 0;
        rLed = 0;
        bLed = 0;
        wtv020sd16p.playVoice(0);
        delay(73000);
        wtv020sd16p.stopVoice();
        break;
      case 'E':
        gLed = 0;
        rLed = 0;
        bLed = 0;
        wtv020sd16p.playVoice(2);
        delay(13000);
        wtv020sd16p.stopVoice();
        break;
      case 'F':
        gLed = 0;
        rLed = 0;
        bLed = 0;
        wtv020sd16p.asyncPlayVoice(3);
        delay(3000);
        break;
      case 'G':
        gLed = 0;
        rLed = 0;
        bLed = 0;
        wtv020sd16p.asyncPlayVoice(1);
        delay(23000);
        break;
      case 'H':
        gLed = 0;
        rLed = 0;
        bLed = 0;
        wtv020sd16p.asyncPlayVoice(4);
        delay(3000);
        break;
    }
  }
  green();
  red();
  blue();
}


void red() {
  if (rLed == 1) {
    for (int dot = 0; dot < NUM_LEDS; dot++) {
      leds[dot] = CRGB::Red;
      FastLED.show();
      leds[dot] = CRGB::Black;
      delay(30);
    }
  }
}

void green() {
  if (gLed == 1) {
    for (int dot = 0; dot < NUM_LEDS; dot++) {
      leds[dot] = CRGB::Green;
      FastLED.show();
      leds[dot] = CRGB::Black;
      delay(30);
    }
  }
}

void blue() {
  if (bLed == 1) {
    for (int dot = 0; dot < NUM_LEDS; dot++) {
      leds[dot] = CRGB::Blue;
      FastLED.show();
      leds[dot] = CRGB::Black;
      delay(30);
    }
  }
}

La seguente istruzione impegna la cpu per 73 secondi, durante questi 73 secondi nessuna altra parte di codice presente nel loop può essere eseguita.

delay(73000);

Gli eventi hardware quando si verificano fanno si che durante i 73 secondi, una determinata funzione specificata venga eseguita. Per fare ciò l'esecuzione del delay(73000) viene interrotta e la cpu esegue un salto verso la funzione specificata, terminata l'esecuzione del codice all'interno della funzione la cpu esegue un salto indietro a ciò che stava facendo prima, cioè il delay.

Quindi riassumendo hai dei problemi con: delay, wire e event driven.
Il più importante dei problemi ti sembrerà strano ma lo hai con event driven vs polling.
Vediamo se riesco a fare un esempio.

polling
Sai che ti arriverà un messaggio sul telefono, quindi per non perderne nessuno stai a guardare il telefono
senza fare altro. Comunque puoi nel frattempo grattarti le p...e. :smiley:

event driven
Stai scrivendo il codice del tuo programma e sei concentrato, ma sai che devono arrivarti dei messaggi sul telefono, allora abiliti una specifica suoneria (la funzione receiveEvent), abiliti il tuo apparato uditivo a ricevere questa suoneria. Quando squilla smetti di scrivere codice e leggi il messaggio e subito dopo ritorni a fare quello che stavi facendo.

Ecco che quando scrivi il codice sei nella funzione loop(), quando squilla sei nella funzione receiveEvent.

Si dice anche che il loop è interrompibile da receiveEvent, questa invece è *non interrompibile", inoltre tutto il tempo passato ad eseguire codice dentro receiveEvent impedisce al codice nel loop di essere eseguito, per cui si prende nota dell'evento e si abbandona receiveEvent rapidamente.

Per il delay guarda l'esempio BlinkWithoutDelay, dove si usa la funzione millis().

Ciao.

Maurotec:
Per il delay guarda l'esempio BlinkWithoutDelay, dove si usa la funzione millis().
Ciao.

Grazie per le spiegazioni :smiley: nei meandri della frustazione sto facendo un corso di c++ per implementare molte lacune che ho..per quanto riguarda lo sketch ho risolto facendo un passo indietro..ovvero il wire.one receive in setup e tra le parentesi tonde mi và a richiamare la funzione void di ricezione..l'unica cosa che ho cambiato che ho aggiunto direttamente lo switch sotto e la gestione delle altre funzioni si articola sulle variabili che varia il master.
Per il delay lo sò che non è il massimo..il problema è che sto usando un riproduttore audio m4a su scheda Sd e sfortunatamente se togliessi il delay eseguirebbe play/stop/play ad ogni ciclo loop.Anche nel reference della libreria non ci sono altre soluzioni se si vuole eseguire un determinato audio nella sd. :confused:

Per rimuovere il delay puoi usare millis come ti ha suggerito Maurotec, ti basta aggiungere la sua gestione ed una variabile che ti dica se hai avviato o meno una riproduzione, se si allora non devi fare nulla (ovvero non farai di nuovo asyncPlayVoice) poi trascorso il tempo necessario affinchè sia terminato il brano (con millis questo controllo lo devi fare) allora farai lo stop e reimposterai la variabile impostandola per dirti che non stai riproducendo nulla.
Ti servirà un po' di studio e pazienza per capire la logica che sta dietro a millis() vs delay ma vedrai che anche per altri progetti ti deverrà quasi indispnsabile padroneggiarla, per capire le basi con cui si usa millis() consiglio prima la lettura di QUESTO post esplicativo, dopo di che lo studio di come si usa la funzione millis(), prima QUI, poi QUI e QUI e QUI e tutti gli articoli che sono in QUESTA pagina ... alla fine il tutto dovrebbe essere più chiaro :slight_smile:

Si a fare un lavoro pulito dovrei far così..però c'è un'altro problema..da quello che ho capito sperimentando nel momento in cui arduino esegue asyncPlayVoice lascia in high qualche pin collegato al modulo(quale sarà? ::slight_smile: ) perchè anche se eseguo il comando una volta e faccio fare altro si interrompe(come se il comando funzionasse come un pulsante di un walkie talkie)..Tornando a noi..potrei capire quale pin viene lasciato in high e usarlo a mio piacimento dentro una funzione di controllo come il millis che mi indicate voi.

Per ora grazie a entrambi :slight_smile:

Non ho capito cosa intenti con fare altro e che legame c'è con l'interruzione della riproduzione.
Ma non sapendo che modulo stai usando non è possibile fornire risposte certe. Metti il link al modulo che sati usando e cerca di dettagliare in modo quanto più preciso possibile la sequenza di oeprazioni che ti fanno interrompere l'esecuzione.
Se poi ho capito bene, se il modulo in questione tiene alto un pin finché non ha terminato la riproduzione non ti serve neanche millis ma ti basta controllare il pin per sapere se hai finito di riprodurre, insomma un'altro stato della macchina a stati finiti :slight_smile:

fabpolli:
ti basta controllare il pin per sapere se hai finito di riprodurre, insomma un'altro stato della macchina a stati finiti :slight_smile:

eheh bisogna capire quale..il modulo è questo può funzionare sia con i pulsanti(ma non salta i file,ovvero,li riproduce in sequenza) oppure nel modo in cui lo sto usando io..dove tra le tonde di asyncplayvoice indico il file presente nella sd.
Le altre funzioni dello slave(come vorrei idealmente che funzionasse) è che mentre riproduce l'audio possa comunque continuare il ciclo for per le luci ma ovviamente essendo presente il delay questo non accade..se riesci a trovare te questo famoso pin sarebbe tutto più semplice perchè poi è lì che potrò insinuarmi con il millis per confrontare quanti tot secondi deve rimanere alto il pin.
Grazie :smiley:

Quale è facile, il link che hai messo parla chiaro il pin 15 del dispositivo segnala la riproduzione di un brano o no, ti basta collegarlo ad un pin di input e verificarlo, non ti serve millis, delay solo un controllo continuo di tale pin per sapere se stai riproducendo o no e usare quest'informazione come meglio credi