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.
Da Bitbucket das Nutzen privater Subdomains (z.B. code.direcs.de) nicht mehr unterstützt, ist der Code zu GitHub umgezogen:
https://github.com/markusk/recopter
Die Links in diesem Beitrag wurden aktualisiert. Danke für den Hinweis von Michael des Technik Magazin | Campus & City Radio 94.4.
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