recopter3 – Kommunikation mit I2C zwischen Arduino und Raspberry Pi mit Python

Wie bereits zuvor erläutert, soll der von der Graupner-Fernbedienung gesteuerte Arduino mit dem Raspberry Pi per I2C kommunizieren. Der hier beschriebene Lösungsansatz ist übrigens vollständig hier herunter ladbar:

/recopter/arduino/graupner1/recopter/recopter.ino für den Arduino

/recopter/raspberry_pi/i2c/recopter.py
 für den Raspberry Pi

Wie man an der Dateiendung .py erkennt, handelt es sich bei dem Code für den RasPi um die Programmiersprache Python. Aber der Reihe nach.

Bei der I2C-Kommunikation gibt es immer nur einen Master, der an verbundene Slaves sendet. Ein angesprochener Slave antwortet dann. Ein Slave sendet nie von selbst Daten. Bei dem von mir gewählten Design ist der Raspberry Pi Master und der Arduino arbeitet als Slave.

Nun zum Code.

Als erstes der Arduino-Teil (Auszüge):

#include     // I2C

#define SLAVE_ADDRESS 0x04

// I2C stuff
int I2Ccommand = 0;
int state = 0;

// I2C commands from RasPi to read out the RC switches / their stored values in the local variables here
int commandFromRasPi = 0;

static int GETMUSICSWITCH  = 0;
static int GETSPEECHSWITCH = 1;

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

  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
}

void loop()
{
  // do what you want here
}

// I2C callback for received data
void receiveData(int byteCount)
{

  while(Wire.available())
  {
    // read I2C value
    I2Ccommand = Wire.read();

    // toggle LED to indicate I2C traffic
    if (state == 0)
    {
        digitalWrite(13, HIGH); // set the LED on
        state = 1;
    }
    else
    {
        digitalWrite(13, LOW); // set the LED off
        state = 0;
    }
  }
}

// I2C callback for sending data
// This is called from the I2C master!
void sendData()
{
    // which command for Arduino?
    if (I2Ccommand == GETSPEECHSWITCH)
    {
      Wire.write(speechState);
      return;
    }

    // which command for Arduino?
    if (I2Ccommand == GETMUSICSWITCH)
    {
      Wire.write(musicState);
      return;
    }

    // unkown command received
    Wire.write(0);
}

Und nun der Raspberry Pi Python Code (Auszüge):

import smbus            # for I2C
import time             # for delay

# the commands from the Arduino
GETMUSICSWITCH  = 0
GETSPEECHSWITCH = 1

# for RPI version 1, use "bus = smbus.SMBus(0)"
bus = smbus.SMBus(1)

# This is the address we setup in the Arduino Program
address = 0x04

def writeNumber(value):
        try:
                bus.write_byte(address, value) # 5 = I/O error
        except IOError, err:
                return -1
        return 0

def readNumber():
        try:
                number = bus.read_byte(address)
        except IOError, err:
                return -1
        return number

# main loop
while True:

    #-----------------------------------------
    # wait until I2C / Arduino becomes ready
    #-----------------------------------------
    while writeNumber(0) == -1:
        time.sleep(2)

    # we are ready now. read the first result, but ignore it
    number = readNumber()

    #-------------------------------
    # first I2C command for Arduino
    # SPEECH
    #-------------------------------
    # write 'command' to Arduino
    writeNumber(GETSPEECHSWITCH)
    time.sleep(0.5)

    # read answer / value from Arduino
    number = readNumber()

    if number != lastSpeechValue:

        # store value
        lastSpeechValue = number

        if number == 1:
                // execute command you want on your Raspberry Pi here.
                // e.g. speak sentence 'A' with espeak.
                # sleep some time
                time.sleep(2)

        if number == 2:
                // execute command you want on your Raspberry Pi here.
                // e.g. speak sentence 'B' with espeak.
                # sleep some time
                time.sleep(2)

    #-------------------------------
    # next I2C command for Arduino
    #-------------------------------
    # write 'command' to Arduino
    writeNumber(GETMUSICSWITCH)
    time.sleep(0.5)

    # read answer / value from Arduino
    number = readNumber()

    [...]

Wie ist nun der Ablauf?

Da der Arduino Slave ist, wartet er die ganze Zeit auf einen I2C-Befehl. Der Master auf dem Raspberry Pi sendet also die Befehle in der while True: Schleife.

Als erstes wird geprüft, ob der I2C-Bus überhaupt bereit ist, also ein Slave da ist. Ist das nicht der Fall (Antwort -1), wird unendlich lang weiter geprüft. Ohne diese Prüfung würde es jedes Mal Fehlermeldungen auf der Konsole des RasPi geben und die folgenden Befehle würden auch nicht funktionieren. Das passiert hier:

#-----------------------------------------
# wait until I2C / Arduino becomes ready
#-----------------------------------------
while writeNumber(0) == -1:
time.sleep(2)

Danach wird die Antwort auf den ersten Befehl vom Arduino gelesen, aber ignoriert:

    # we are ready now. read the first result, but ignore it
    number = readNumber()

Nun wird der erste „richtige“ Befehl an den Arduino gesendet. Er lautet „gib mir den Status vom ’speech switch‘ – also genauer den Status eines einzelnen Schalters auf der Graupner-Fernsteuerung.

    #--------
    # SPEECH
    #--------
    # write 'command' to Arduino
    writeNumber(GETSPEECHSWITCH)
    time.sleep(0.5)

Dieser Befehl wird im Arduino dann vollautomatisch hier entgegen genommen und in der Variable I2Ccommand gespeichert:

// I2C callback for received data
void receiveData(int byteCount)
{

  while(Wire.available())
  {
    // read I2C value
    I2Ccommand = Wire.read();
  }
}

Der Arduino antwortet dann nicht in Hauptschleife loop, sondern automatisch innerhalb der Methode sendData. Hier sendet dann den in der main loop ständig ausgelesenen Status der RC-Schalter:

// I2C callback for sending data
// This is called from the I2C master!
void sendData()
{
    // which command for Arduino?
    if (I2Ccommand == GETSPEECHSWITCH)
    {
      Wire.write(speechState);
      return;
    }

    // which command for Arduino?
    if (I2Ccommand == GETMUSICSWITCH)
    {
      Wire.write(musicState);
      return;
    }

    // unkown command received
    Wire.write(0);
}

Als nächstes nimmt der Raspbery Pi die Antwort des Arduino in der while True: entgegen:

    # read answer / value from Arduino
    number = readNumber()

Danach werden auf dem RasPi die Antworten abgearbeitet – aber nur, wenn die Antwort sich gegenüber der vorigen geändert hat!

    if number != lastSpeechValue:

[...]

Alles klar?

Wie man das Python Programm unter Linux in den „Autostart“ bringt, wird ein nächster Blog-Eintrag zeigen.

 

2 Gedanken zu „recopter3 – Kommunikation mit I2C zwischen Arduino und Raspberry Pi mit Python“

  1. Hallo Markus
    Ich habe deinen Beitrag für die Ansteuerung von einer Graupner Fernsteuerung über Uno zum Pi gelesen.
    Ich möchte gerne vier Servos steuern, und wenn der der Sender ausfällt oder ich außer Reichweite des Senders bin, dass ein BMP085 die Höhe hält, und ein MPU-6050 die und Horizonttal die Lage hält.
    Leider scheitert ich schon im Ansatz bei der Kommunikation-mit-i2c-zwischen-arduino-und-raspberry-pi-mit-python.
    Kannst du mir helfen?
    Mit freundlichen Grüßen

    Ulrich

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert