IR Fernbedienung mit Relais Modul und Portexpander MCP23017

Waren Sie schon einmal im Miniatur-Wunderland in Hamburg? Dort ist die Welt im Maßstab 1:87 zu sehen. Es lohnt sich für alle zwischen acht und achtzig. Nach meinem letzten Besuch habe ich meine eigene Modelleisenbahn wieder aufgebaut. Nun will ich versuchen, mit meinem Wissen über Mikrocontroller einiges auf meiner kleinen Anlage zu automatisieren. Am liebsten würde ich die Weichen und Lichtstromkreise über eine Infrarot-Fernbedienung schalten. In meiner Schrottkiste habe ich noch mehrere Fernbedienungen von ausgemusterten CD/DVD-Spielern usw. liegen. Müsste doch hinhauen.

Vorüberlegungen

Die Weichen sind permanent mit Lichtstrom (12 – 18Volt) verbunden. Um die eingebauten Elektromagnete zu schalten, wird mit einem Taster kurzzeitig die Verbindung zu Masse hergestellt. Aufgrund der o.g. Spannung bietet es sich an, Relais zu verwenden und zwar zwei je Weiche bzw. Weichenpaar, wenn zwei Weichen gleichzeitig geschaltet werden. Da kommt schnell eine gewisse Anzahl zusammen. Zum Glück gibt es die Relais-Module mit 8 oder 16 Relais.

So viele GPIO-Pins hat ja kaum ein Mikrocontroller, also klarer Fall für den Port Expander MCP23017, den ich vor zwei Jahren bereits in einem dreiteiligen Blog vorgestellt hatte (Teil 1, Teil 2, Teil 3). Mit einem Port Expander erhält man sechzehn zusätzliche GPIOs, genug für acht Weichen. Aus Gründen der Übersichtlichkeit plane ich einen zweiten Port Expander für die Lichtstromkreise, bei denen ja nicht nur ein kurzer Masse-Impuls geschaltet wird, sondern das Licht ein- bzw. ausgeschaltet werden soll. Bis zu acht Port Expander kann man parallel schalten, da die I2C-Adresse im Bereich 0x20 bis 0x27 wählbar ist.

Für die Infrarot-Fernbedienung benötige ich einen IR-Empfänger und eine passende Programm-Bibliothek. Ich favorisiere das kleine Infrarot-Sensor-Modul, das einzeln erhältlich, oder Teil des 35 in 1 Sensoren-Kits ist. Diese kleinen Module haben eine LED, die beim Signal-Empfang blinkt. Das schließt bei den Versuchen und im Betrieb eine Fehlerquelle aus, wenn etwas nicht klappt. Grundsätzlich genügt auch der einzelne IR-Sensor, z.B. TSOP4838 oder VS1838, preislich macht es jedoch keinen Unterschied.

Hardware

1

Mikrocontroller ATmega 328

1

Infrarot-Sensor-Modul

opt. 

35 in 1 Sensoren-Kit

1

16-Relais Modul 12V mit Optokoppler

und/oder

8-Relais Modul 5V mit Optokoppler 

und/oder

4-Relais Modul 5V mit Optokoppler

1 - 2

Port Expander MCP23017 CJMCU-2317 alternativ klick

1 - 2

10 kΩ-Widerstand

1

(Mini-)Breadboard, Jumperkabel


Schaltung und Sketch für meinen IR-Empfänger

Zunächst installiere ich in der Arduino IDE die Bibliothek IRremote von Armin Joachimsmeyer. Der Anschluss der drei Pins sollte nicht allzu schwierig sein.

Aus der Liste der Beispielprogramme schaue ich mir zunächst SimpleReceiver an, um erste Versuche mit meiner Philips-IR-Fernbedienung zu machen.

Zu Beginn stehen viele auskommentierte Zeilen für die Fernbedienungen unterschiedlicher Hersteller. Im Kommentar steht, dass alle Protokolle aktiv sind, wenn kein spezielles definiert wurde.

 /*
  * Specify which protocol(s) should be used for decoding.
  * If no protocol is defined, all protocols are active.
  */
 //#define DECODE_DENON       // Includes Sharp
 //#define DECODE_JVC
 //#define DECODE_KASEIKYO
 //#define DECODE_PANASONIC   // the same as DECODE_KASEIKYO
 //#define DECODE_LG
 #define DECODE_NEC          // Includes Apple and Onkyo
 //#define DECODE_SAMSUNG
 //#define DECODE_SONY
 //#define DECODE_RC5
 //#define DECODE_RC6
 
 //#define DECODE_BOSEWAVE
 //#define DECODE_LEGO_PF
 //#define DECODE_MAGIQUEST
 //#define DECODE_WHYNTER
 
 //#define DECODE_DISTANCE     // universal decoder for pulse width or pulse distance protocols
 //#define DECODE_HASH         // special decoder for all protocols
 
 //#define DEBUG // Activate this for lots of lovely debug output from the decoders.
 //#define INFO               // To see valuable information from universal decoder for pulse width or pulse distance protocols

Die letzte Zeile hört sich vielversprechend an, denn den Namen Philips habe ich in den Zeilen davor nicht gefunden. Diese Zeile werde ich auskommentieren, um weiterführende Informationen zu meiner Fernbedienung zu erhalten. Aber wo muss ich den Daten-Pin anschließen? Auf den ersten Blick sehe ich keine entsprechende Zeile im Sketch. Aber auf den zweiten.

Im Sketch befindet sich die Zeile:

 #include "PinDefinitionsAndMore.h"

Im gleichen Unterverzeichnis wie der Sketch befindet sich die Datei PinDefinitionsAndMore.h, die zusammen mit dem Sketch geöffnet und mit der o.g. Zeile inkludiert wird. Hier finde ich den Pin 2 für meinen AVR Mikrocontroller.


Also, minus (-) an GND, den mittleren Pin an 3,3V oder 5V und den Signal-Pin S an Pin 2 des Mikrocontrollers. 

Beim Drücken der Taste 1 erhalte ich folgende Ausgabe im Seriellen Monitor:

Zwei Zeilen erhalte ich nur, weil ich die Taste „ordentlich“ gedrückt habe, d.h. etwas länger, so dass tatsächlich schon zwei IR-Signale gesendet wurden. Wenn ich eine Taste nur ganz kurz antippe, erhalte ich nur eine Zeile.

Wichtig sind für mein Projekt die Angaben hinter Protocol=, bei der entsprechenden Zeile mit RC6 werde ich die Kommentarstriche // entfernen, sowie der Wert hinter Command=, der den Code für die gedrückte Taste enthält. 

An dieser Stelle notiere ich mir die jeweiligen Werte hinter Command= für die Tasten, die ich später verwenden möchte. 

Das war alles noch einfacher, als ich es mir vorgestellt hatte. Danke an Armin Joachimsmeyer für seine Programmbibliothek.

Nun wende ich mich dem Port Expander MCP23017 zu. Dazu lese ich noch einmal die Grundlagen im Teil 1 meiner Blog-Serie vom 20. Juli 2020. Hier eine kurze Wiederholung:

Die 28 Anschlüsse sind schnell erklärt, denn 16 davon (GPB0 bis GPB7 und GPA0 bis GPA7) sind unsere hinzugewonnenen GPIO-Pins. Spannungsversorgung VDD (Pin9) geht an 3,3V oder 5V, VSS (Pin10) an GND, Anschlüsse 11 bis 14 sind die Datenleitungen. I2C benötigt nur SDA (13) und SCL (12), 11 und 14 sind nicht angeschlossen (NC=not connected, diese werden für den Port Expander mit SPI, den MCP23S17 benötigt). 

Über die Anschlüsse A0 bis A2 kann man, wie oben erwähnt, die I2C-Adresse vorwählen. Wenn alle drei an GND (LOW) anliegen, lautet die I2C-Adresse 0x20. Dies ist die Basisadresse, durch Anschluss einzelner Pins an HIGH wird dann die sich daraus ergebende Zahl addiert. Beim zweiten Port Expander werden wir A0 an HIGH legen, um Adresse 0x21 zu erhalten.

Der Querstrich über dem RESET bei Anschluss 18 bedeutet wie üblich die Negation des Eingangs. Wenn der IC resettet werden soll, muss dieser Anschluss kurzzeitig an GND/LOW angeschlossen werden. Das bedeutet im Umkehrschluss, dass hier im Normalbetrieb HIGH anliegen muss. Um beim Reset einen Kurzschluss zu vermeiden, verbinden wir Pin 18 über einen Pullup-Widerstand von 10 kOhm mit VDD

Die Anschlüsse INTA (20) und INTB (19) sind für sogenannte Interrupts vorgesehen. Diese werde ich nicht verwenden.

Die Programmierung und der Datenaustausch erfolgen beim MCP23017 über Register mit 8 Bits. Ein umfangreiches Kapitel, das wir durch die Verwendung einer Programm-Bibliothek deutlich abkürzen können. An dieser Stelle wird aus dem 42-seitigen Datenblatt nur zitiert, was tatsächlich bei diesem Projekt verwendet wird.

Zur Einstimmung installiere ich die Programm-Bibliothek MCP23017 von Bertrand Lemasle und schaue mir die Dateien MCP23017.h und MCP23017.cpp sowie das Beispiel-Programm PortCopy an, bei dem auf Tastendruck an einem Eingang von Port B der korrespondierende Aushang an Port A eingeschaltet wird.


 /**
  * On every loop, the state of the port B is copied to port A.
  *
  * Use active low inputs on port A. Internal pullups are enabled by default by the library so there is no need for external resistors.
  * Place LEDS on port B for instance.
  * When pressing a button, the corresponding led is shut down.
  *
  * You can also uncomment one line to invert the input (when pressing a button, the corresponding led is lit)
  */
 #include <Wire.h>
 #include <MCP23017.h>
 
 #define MCP23017_ADDR 0x20
 MCP23017 mcp = MCP23017(MCP23017_ADDR);
 
 void setup() {
     Wire.begin();
     Serial.begin(115200);
     
     mcp.init();
     mcp.portMode(MCP23017Port::A, 0);          //Port A as output
     mcp.portMode(MCP23017Port::B, 0b11111111);   //Port B as input
 
     mcp.writeRegister(MCP23017Register::GPIO_A, 0x00);  //Reset port A
     mcp.writeRegister(MCP23017Register::GPIO_B, 0x00);  //Reset port B
 
     // GPIO_B reflects the same logic as the input pins state
     mcp.writeRegister(MCP23017Register::IPOL_B, 0x00);
     // Uncomment this line to invert inputs (press a button to lit a led)
     //mcp.writeRegister(MCP23017Register::IPOL_B, 0xFF);
 }
 
 void loop() {
     uint8_t currentB;
 
     currentB = mcp.readPort(MCP23017Port::B);
     mcp.writePort(MCP23017Port::A, currentB);
 }

Die ersten vier Zeilen des Programms kann ich komplett übernehmen und in das IR-Programm integrieren. Die Bibliothek Wire.h wird für I2C benötigt, mit der Bibliothek MCP23017.h wird das Objekt mcp an der Adresse 0x20 instanziiert.

 #include <Wire.h>
 #include <MCP23017.h>
 
 #define MCP23017_ADDR 0x20
 MCP23017 mcp = MCP23017(MCP23017_ADDR);

Für mein Projekt benötige ich alle GPIO-Ports als Ausgänge. Dafür finden nur die Methoden   mcp.init(), mcp.portMode(), mcp.writeRegister() und mcp.writePort() Anwendung.

Mit mcp.init() wird zunächst das besondere Register IOCON (für I/O control) mit festgelegten Werten beschrieben. Diese Anweisung müssen wir in der Funktion setup() nach der Initialisierung von I2C einfügen.

Mit mcp.portMode(MCP23017Port::A, 0b00000000); wird festgelegt, dass die jeweils acht Anschlüsse (hier GPA0 bis GPA7) als Ausgang fungieren. Für Port B gibt es zwei Wahlmöglichkeiten: Einerseits kann man mit mcp.portMode(MCP23017Port::B, 0b11111111); wie im Beispielsketch den Port B mit acht Eingängen festlegen und dann das konventionelle Schaltpult der Eisenbahnanlage parallel schalten. Oder man definiert Port B als Ausgang und kann weitere acht Relais anschließen.

Mit mcp.writePort(MCP23017Port::A, 0bxxxxxxxx); beschreiben wir den Port mit den gewünschten Werten, um jeweils ein Relais zu schalten. Mehr dazu gleich.

Als Zwischenergebnis füge ich also die beiden Beispiel-Sketche SimpleReceiver und PortCopy zusammen. Nun muss ich nur noch in der Funktion loop() abfragen, welche Taste der IR-Fernbedienung gedrückt wurde und in if - bzw. else if  - Verzweigungen einen Code zuweisen, mit dem jeweils mit Hilfe des Port Expanders das richtige Relais geschaltet wird. Dazu schauen wir uns den Befehl mcp.writePort(MCP23017Port::A, 0bxxxxxxxx); erneut an. Jedes x steht für eine 0 oder 1 an den Ausgängen GPA7, GPA6, …, GPA1, GPA0. Als Eingabe können wir die jeweilige Zweierpotenz des Ausgangs setzen, also 128, 64, …, 2, 1. Diese Zweierpotenzen sind also die Codes für unsere acht Relais.

Aufgrund der negativen Logik beaufschlage ich das gewünschte Relais mit dem Wert 255-code, und zwar nur für 500ms. Danach werden alle Relais wieder zurückgesetzt.

     mcp.writePort(MCP23017Port::A,255-code);
     delay(500);
     mcp.writePort(MCP23017Port::A, 255);

Hier der gesamte Code für meinen Versuchsaufbau mit acht Relais. Tatsächlich habe ich die Tests nur mit zwei Weichen und vier Relais durchgeführt. Siehe Bild unten.

Sketch Download

 #define DECODE_RC6           // works fine with my Philips DVD Remote Controller
 #include <Arduino.h>
 /*
  * Define macros for input and output pin etc.
  */
 #include "PinDefinitionsAndMore.h"
 #include <IRremote.hpp>
 
 // new lines for MCP23017 Port Expander
 #include <Wire.h>
 #include <MCP23017.h>
 
 #define MCP23017_ADDR 0x20
 MCP23017 mcp = MCP23017(MCP23017_ADDR);
 
 int code = 0;
 
 void setup() {
     Wire.begin();
     Serial.begin(115200);
     // Just to know which program is running on my Arduino
     Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE));
 
     // Start the receiver and if not 3. parameter specified, take LED_BUILTIN pin from the internal boards definition as default feedback LED
     IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
 
     Serial.print(F("Ready to receive IR signals of protocols: "));
     printActiveIRProtocols(&Serial);
     Serial.print(F("at pin "));
     Serial.println(IR_RECEIVE_PIN);
 // new lines for MCP23017 Port Expander
     mcp.init();
     mcp.portMode(MCP23017Port::A, 0b00000000);          //Port A as output
 //   mcp.portMode(MCP23017Port::B, 0b11111111); //Port B as input
     mcp.writeRegister(MCP23017Register::GPIO_A, 0b11111111);  //Set port A to HIGH for relays
 //   mcp.writeRegister(MCP23017Register::GPIO_B, 0b00000000); //Reset port B
 
     // GPIO_B reflects the same logic as the input pins state
     //mcp.writeRegister(MCP23017Register::IPOL_B, 0x00);
     // Uncomment this line to invert inputs (press a button to lit a led)
 //   mcp.writeRegister(MCP23017Register::IPOL_B, 0xFF);    
 }   // end of void setup()
 
 
 void outputCode(int code)   {
     Serial.print("Code = ");
     Serial.println(code);
     mcp.writePort(MCP23017Port::A,255-code);
     delay(500);
     mcp.writePort(MCP23017Port::A, 255);
 }    // end of function
 
 void loop() {
     if (IrReceiver.decode()) {
         // Print a short summary of received data
         IrReceiver.printIRResultShort(&Serial);
         if (IrReceiver.decodedIRData.protocol == UNKNOWN) {
             // We have an unknown protocol here, print more info
             IrReceiver.printIRResultRawFormatted(&Serial, true);
        }
         Serial.println();
         /*
          * !!!Important!!! Enable receiving of the next value,
          * since receiving has stopped after the end of the current received data packet.
          */
         IrReceiver.resume(); // Enable receiving of the next value
         /*
          * Finally, check the received data and perform actions according to the received command
          */
         int received = IrReceiver.decodedIRData.command;
         Serial.print("received = ");
         Serial.println(received);
         
         if (IrReceiver.decodedIRData.command == 1) {
           code = 1;
           outputCode(code);
        }
         else if (IrReceiver.decodedIRData.command == 3) {
           code = 2;
           outputCode(code);
        }
         else if (IrReceiver.decodedIRData.command == 4)   {
           code = 4;
           outputCode(code);
            }
         else if (IrReceiver.decodedIRData.command == 6)   {
           code = 8;
           outputCode(code);
          }
         else if (IrReceiver.decodedIRData.command == 7) {
           code = 16;
           outputCode(code);
        }
         else if (IrReceiver.decodedIRData.command == 9) {
           code = 32;
           outputCode(code);
        }
         else if (IrReceiver.decodedIRData.command == 131)   {
           code = 64;
           outputCode(code);
            }
         else if (IrReceiver.decodedIRData.command == 239)   {
           code = 128;
           outputCode(code);
          }          
         else   {
           code = 0;
           Serial.print("Code = ");
           Serial.println(code);
          }
 //       delay(500);       //avoid double input
    }
 }   // end of void loop()

Denken Sie bitte an die Datei PinDefinitionsAndMore.h im gleichen Verzeichnis.

Auf mehrfachen Wunsch hier noch der Schaltplan:

Schaltplan

Ein Darlington Transistor ist nicht notwendig, jedoch eine externe Spannungsversorgung für die Relais, wenn es mehr als zwei sind. Bitte auch einen Blick in die älteren Beiträge werfen, die oben verlinkt wurden.

P.S. Meine Frau hatte etwas dagegen, dass ich den Wohnzimmertisch anbohre, um die Kabel verschwinden zu lassen. Auf der Anlage befinden sich MCU, Relais und Kabel unterhalb der Platte.

Für arduinoProjekte für anfänger

7 Kommentare

Bernd Albrecht

Bernd Albrecht

@ H.-J. Spitzner zum Thema Relais: Danke für den Hinweis, dass Relais – wie auch Motoren und Servos – am besten aus einer externen Spannungsquelle versorgt werden. Dafür wird m.E. jedoch kein ULN 2803 benötigt, denn die Relais-Module bieten für die externe Spannung einen Anschluss, meist gebrückt mit einem Jumper. Wenn dieser entfernt wird, kann man hier die Spannung, z.B. 6V, direkt anschließen.

Andreas Wolter

Andreas Wolter

wir haben einen Schaltplan ergänzt. Bitte auch einen Blick in die verlinkten Blogbeiträge werfen.

Grüße,
Andreas Wolter i.A. Bernd Albrecht
AZ-Delivery Blog

Hans-Georg Müller

Hans-Georg Müller

Moin,
wie immer Klasse. Aber wie Hans schon schrieb, der Schaltplan wäre schon wichtig bei der Menge von Bauteilen.
Danke

Michael Arnold

Michael Arnold

Vielen Dank für das Super Programm. Habe es Heute ausprobiert und es Funktioniert einwandfrei.

H.-J. Spitzner

H.-J. Spitzner

Ich habe dies in einer Anlage ähnlich gehandhabt, allerdings fällt mir ein Problem auf, dies ist die Variante mit dem “delay(500)”. Wenn zuviele Relais geschaltet werden sollen, ist diese Arbeitsweise etwas hinderlich. Ich habe dazu jedem Ausgang (ob Relais oder etwas anderes ) einen eigenen Timer (z.B. Zähler im Sekundentakt), welcher dann die Rücksetzung macht. Dadurch sind die Kommando-Eingabe sofort wiederfrei. Außerdem ist es hier wichtig, da 6 * 328p und 1 MEGA (196 Tasten und LED) in einem Netzwerk (RS485) sich gegenseitig Datensätze zu senden, da sollten keine delay’s, die Abfragen behindern. Wenn man vorher plant wieviel Relaise reinkommen und der Prozeß konkret ist, sollte es laufen, aber später alles umbauen (software neu schreiben) dauert länger.

P.S. als Nachtrag: Bei der Schaltung zum STeuern von Relais usw. sollte man auch den Strombedarf in Betracht ziehen! Als Ergänzung ist hier der ULN 2803 (8*Darlington-Transistorpaar mit Open_Kollektor) zu nennen.
viel Erfolg!

Hans

Hans

Danke für die ausführliche Zusammenfassung. Eine Frage: Wo kann ich den Schaltplan finden? Oder habe ich es nur überlesen?
Mit freundlichem Gruß Hans

jordi

jordi

Apreciades proveïdores, agradezco la càlida de sus productors y desfaria comentar que encuentro a faltar documentacion en Castellanos y a poder ser en catalana se, el esfuerzo que esto supone però para gente inexperta supone una gran ajuda. Gràcies

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert