Sheet Arduino n.9 (Arduino I2C communication)

[Lezione del 09 e 10 dic 2019]

Comunicazione I2C tra Arduino

La scheda della esercitazione n.9, richiede di effettuare comunicazione tra due microcontrollori Arduino utilizzando il bus \(I^2C\), che nel seguito indicheremo come I2C.

Come con l’esercizio con le seriali (la scheda n.8) dovremo utilizzare due microcontrollori. A differenza delle porte seriali, che collegano i sistemi in modo paritetico ma solo in modalità punto-punto, con il bus I2C dobbiamo scegliere per forza di assegnare un ruolo master ad un device e di slave a tutti gli altri device collegati al master.

La scheda è suddivisa in due parti. Mi sono impegnato direttamente nella implementazione della seconda parte, che include anche i concetti implementati nella prima parte.

In sintesi, si tratta di implementare uno slave che controlla un led e un pulsante. E di comandarlo dal master, che invia richieste dello stato del pulsante e comandi di accensione/spegnimento del led, tramite il bus I2C.

Il seguente video sintetizza l’esercizio:

Anche in questo caso abbiamo due sketch: uno per lo slave e uno per il master.

E anche in questo caso cominciamo con lo slave:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* Embedded Systems Architecture - hardware
 *  application on xx yyy 2019
 *  
 *  I2C communication; control blink on BUS
 *
 *  this is slave; it executes commands, 
 *   - reading status of a button and sending it to master
 *   - powering on/off a led
*/

#include <Wire.h>

#define BUTTON   2  //port to read button
#define LED      9  //port to drive led
#define MYADDR   7  //I2C address of this device

void setup() {
    pinMode(BUTTON, INPUT);
    pinMode(LED, OUTPUT);
    Wire.begin(MYADDR);        // init I2C as client addr.7 
    Wire.onRequest(answer);    //      on I2C request for data, this is the answer funciton
    Wire.onReceive(receive);   //      on I2C send data, this is the receive function
}

void loop() {
    delay(1000);
}

/* on master request, client sends button status: ok/ko */
void answer(){
    if(digitalRead(BUTTON) == HIGH) Wire.write("ok");
    else Wire.write("ko");
}

/* if master sends data, it is the power on/off command about the led */
void receive(){
    String cmd = "";
    while(Wire.available()) cmd+= char(Wire.read());

    if(cmd == "on") digitalWrite(LED, HIGH);
    else if (cmd == "off") digitalWrite(LED, LOW);
}

Che è molto semplice: inizializzo nel setup (saltiamo le solite porte digitali) il gestore del bus alla ln 20, indicando l’indirizzo da assegnare. La presenza dell’indirizzo configura l’unità come uno slave. Dopo di che indico al gestore del bus le routine per rispondere alle richieste del master (ln 21) e per ricevere dati dal master (ln 22). Sono rispettivamente le funzioni answer e recive.

La answer (ln da 29 a 32) invia lo stato del pulsante: un ok se chiuso e un ko se aperto.

La receive (ln da 35 a 42) accumula i caratteri inviati dal master nel buffer cmd. Dopo di che lo analizza: se contiene il comando on accende il led. Se vi è il comando off lo spegne. Altri comandi sono ignorati.

Il loop (ln da 25 a 27) è un ritardo puro: lo slave passa il tempo ad attendere l’arrivo di comandi dal master.

Invece il master è il seguente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* Embedded Systems Architecture - hardware
 *  application on xx yyy 2019
 *  
 *  I2C communication; control blink on BUS
 *
 *  this is master; it sends commands:
 *    - read button status on slave and print it on serial0
 *    - power on led on slave for 500ms
 *    - power off led on slave for 500ms
 *    - loop
 */

#include <Wire.h>

#define SLAVE_ADDR 7

void setup() {
    Serial.begin(9600);
    Wire.begin();       // init I2C; no address, hence this is master
}

void loop() {
    /* request to slave about button status: 2 bytes 
     *   then writes the received status to Serial0
     */
    Wire.requestFrom(SLAVE_ADDR, 2);
    while(Wire.available()){
        char c = Wire.read();
        Serial.print(c);
    }
    Serial.println();
    
    /* sends to slave command to power on the led */
    Wire.beginTransmission(7);
    Wire.write("on");
    Wire.endTransmission();
    delay(500);
    
    /* sends to slave command to power off the led */
    Wire.beginTransmission(7);
    Wire.write("off");
    Wire.endTransmission();
    delay(500);
}

In questo caso nel setup inizializziamo una seriale verso il PC, su cui invieremo lo stato del pulsante, e il gestore del bus I2C. In questo caso alla ln 19 non indichiamo un indirizzo. Questo designa l’unità come master: tutto ciò che viaggia sul bus è in seguito a sollecitazioni di questo microcontrollore.

Ora che ho il potere, lo uso.

Nel loop effettuo:

  • una richiesta allo slave per conoscere lo stato del pulsante (ln 26) seguita dall’accumulo della risposta (il while alle ln da 27 a 30) subito inviata carattere per carattere al PC via seriale;

  • invio allo slave un comando di accensione del led (ln da 34 a 36); dopo una pausa di 500ms,

  • invio allo slave un comando di spegnimento del led (ln da 40 a 43).

Essendo un loop l’insieme predetto è rieffettuato ciclicamente.