Calliope Mini und I2C - MCP23008


Gepostet Oktober 2017, Kategorie: I2C & SPI


Calliope Mini und I2C - MCP23008


Inhaltsverzeichnis


    Grundlagen, wie eine I2C-Kommunikation zwischen einem Mikrocontroller und dem Portexpander MCP23008 hergestellt wird, werden in den folgenden beiden Posts vermittelt. In diesem Post wird der Portexpander MCP23008 und dessen großer Bruder MCP23017 über einen Arduino angesteuert.

    Arduino und I2C - MCP23017
    Arduino und I2C - MCP23017 und MCP23008

    Aufbau

    Foto des Aufbau Schaltplan

    Vom Calliope Mini werden vier Kabel zum Steckbrett mit dem Portexpander geführt: Masse, 3.3 V und die beiden Anschlüsse der I2C-Schnittstelle: SDA und SCL. Diese beiden Anschlüsse werden direkt mit dem Portexpander verbunden.

    Die Adresse des Portexpanders wird durch die Pins A0, A1, A2 eingestellt. Da diese alle auf L-Pegel liegen, ist die Adresse des MCP23008 0x20. Diese Adresse muss im Programm eingestellt werden, damit eine I2C-Verbindung vom Calliope aufgebaut werden kann.

    Der Reset-Pin des MCP23008 befindet sich auf H-Pegel, damit der MCP23008 nicht resettet wird und verwendet werden kann.

    An den GPIO-Pins GP0 und GP1 ist jeweils eine LED mit Vorwiderstand angeschlossen. Zwei Taster sind an den Pins GP6 und GP7 angeschlossen.

    Programmierung von Ausgängen

    Programmierung

    Das folgenden Beispielprogramm lässt die am Portexpander angeschlossen LEDs im Wechsel blinken. Dieses Programm muss in einem neuen Projekt der PXT-Programmierumgebung (http://pxt.calliope.cc/index.html) im JavaScript-Quelltext eingefügt werden. Anschließend sollte die Block-Ansicht nicht mehr aufgerufen werden oder man löscht direkt im Explorer die Datei main.blocks.

    const ADDR = 0x20;
    const REG_IODIR = 0x00;
    const REG_GPPU = 0x06;
    const REG_GPIO = 0x09;
     
    writeRegister(ADDR, REG_IODIR, 0xfc);
     
    basic.forever(() => {
        writeRegister(ADDR, REG_GPIO, 0x01);
        basic.pause(500);
        writeRegister(ADDR, REG_GPIO, 0x02);
        basic.pause(500);
    })
     
    function writeRegister(addr: number, reg: number, value: number) {
        pins.i2cWriteNumber(addr, reg * 256 + value, NumberFormat.UInt16BE)
    }

    In den ersten Zeilen findet die Konstantendefinition statt. In der Konstante ADDR wird die I2C-Adresse des Portexpanders abgelegt. Die folgenden Konstanten sind die Adressen der Register innerhalb des Portexpanders.

    Die Programmzeile writeRegister(ADDR, REG_IODIR, 0xfc) sorgt dafür, dass die beiden LED-Pins als Ausgängen geschalten werden. Dies geschieht, indem in das Register IODIR der Wert 0xfc geladen wird. 0xfc ist als Binärzahl 0b11111100. Aus diesem Grund werden die Pins GP0 und GP1 zu Ausgängen.

    Die Funktionalität der Funktion writeRegister() wird im nächsten Unterabschnitt verdeutlicht.

    Innerhalb der Unendlichschleife wird nun abwechselt der Wert 0x01 und 0x02 in das GPIO-Register geladen. Da gilt 0x01 = 0b00000001 und 0x02 = 0b00000010 werden die LEDs abwechselt angeschalten.

    Setzen von Registern

    Das Setzen eines Registers findet innerhalb der Funktion writeRegister() statt. Grundlagen hierfür sind im Post Arduino und I2C - MCP23017 zu finden.

    Um den Inhalt eines Registers zu setzen, müssen zwei Bytes über die I2C-Schnittstelle an den Portexpander gesendet werden. Das erste Byte enthält die Adresse des Registers und das zweite Byte die Daten, welche in das Register geschrieben werden sollen.

    Durch die Anweisung reg * 256 + value werden Adresse und Daten aneinandergehangen und es entsteht eine 16-Bit-Zahl. Dies erklärt den ersten Teil des letzten Argumentes: NumberFormat.UInt16

    Die Abkürzung BE steht für Big Endian und beschreibt die Reihenfolge, in der die zwei Bytes der entstandenen 16-Bit-Zahl über die I2C-Schnittstelle gesendet werden sollen (siehe https://de.wikipedia.org/wiki/Byte-Reihenfolge).

    Wird Big Endian eingestellt, so wird zuerst das höchstwertige Byte, also die Adresse versendet und anschließend das niedrigstwertige Byte, also die Daten, gesendet.

    Aufnahme mit Logikanalysator

    Aufnahme mit Logikanalysator

    Während des Setzen des GPIO-Registers auf den Wert 0x02 wurde diese Aufnahme mit dem Logikanalysator gemacht. Es sind kaum Unterschiede zur Aufnahme zu erkennen, die bei der Kommunikation zwischen Arduino und Portexpander entstanden ist.

    Wie beim Arduino dauert die Kommunikation etwa an. Auffallend sind jedoch kleine Spikes, die zu Beginn des ACK-Bit im Signal entstehen. Diese haben anscheinend aber keinen Einfluss auf die I2C-Kommunikation.

    Programmierung von Eingängen

    Programmierung

    Das folgende Beispielprogramm soll auf das Drücken des Tasters an GP7 reagieren. Ist dieser Taster gedrückt, so soll die LED an GP1 leuchten, ansonsten leuchtet die LED an GP0.

    const ADDR = 0x20;
    const REG_IODIR = 0x00;
    const REG_GPPU = 0x06;
    const REG_GPIO = 0x09;
     
    writeRegister(ADDR, REG_IODIR, 0xfc);
    writeRegister(ADDR, REG_GPPU, 0xc0);
     
    basic.forever(() => {
        let value = readRegister(ADDR, REG_GPIO);
     
        if (value & 0x80) {
            writeRegister(ADDR, REG_GPIO, 0x01);
        } else {
            writeRegister(ADDR, REG_GPIO, 0x02);
        }
    })
    function writeRegister(addr: number, reg: number, value: number) {
        pins.i2cWriteNumber(addr, reg * 256 + value, NumberFormat.UInt16BE)
    }
    function readRegister(addr: number, reg: number): number {
        pins.i2cWriteNumber(addr, reg, NumberFormat.Int8LE);
        return pins.i2cReadNumber(addr, NumberFormat.Int8LE)
    }

    Zu Beginn des Programmes wird wie beim vorhergehenden Programm das Register IODIR auf den Wert 0xfc gesetzt, um die beiden LED-Pins als Ausgänge zu deklarieren. Durch das Setzen des Registers GPPU auf den Wert 0xc0, der 0b11000000 entspricht, werden an den Pins der beiden Taster die internen Pullup-Widerstände aktiviert.

    Durch die Anweisung readRegister(ADDR, REG_GPIO) wird der Wert des GPIO-Registers ausgelesen. Dieser wird anschließend in der Variablen value abgelegt.

    Durch die Bedingung value & 0x80 kann geprüft werden, ob das höchstwertige Bit dieses Register 0 oder 1 ist und damit kann bestimmt werden, ob sich der Pin des Tasters auf L- oder H-Pegel befindet.

    Ist der Taster gedrückt, so ist der Pin auf L-Pegel und der else-Block wird ausgeführt. Das Register GPIO wird auf den Wert 0b00000010 gesetzt und die zweite LED leuchtet.

    Ist der Taster nicht gedrückt, so wird das Register GPIO auf den Wert 0b00000001 gesetzt und die erste LED leuchtet.

    Lesen von Registern

    Das Lesen von Registern wird durch die Funktion readRegister() übernommen. Um ein Register vom MCP23008 zu lesen, werden zwei I2C-Nachrichten über die I2C-Schnittstelle ausgetauscht.

    In der ersten Nachricht wird dem Portexpander innerhalb eines Bytes die Adresse des Registers übertragen, welches gelesen werden soll. Diese Nachricht wird durch die Anweisung pins.i2cWriteNumber(addr, reg, NumberFormat.Int8LE) gesendet.

    In der zweiten Nachricht wird der Portexpander aufgefordert ein Byte an den Mikrocontroller zu übertragen. Dieses Byte wird von der Funktion readRegister() zurückgegeben. Daher resultiert der folgende Befehl: return pins.i2cReadNumber(addr, NumberFormat.Int8LE)

    Beispielprogramm: Fußgängerampel

    Schaltung

    GPIO Funktion
    0 Autos grün
    1 Autos gelb
    2 Autos rot
    3 Fußgänger grün
    4 Fußgänger rot
    7 Schalter

    Programmierung

    const ADDR = 0x20;
    const REG_IODIR = 0x00;
    const REG_GPPU = 0x06;
    const REG_GPIO = 0x09;
    writeRegister(ADDR, REG_IODIR, 0b11100000);
    writeRegister(ADDR, REG_GPPU, 0b10000000);
     
    basic.forever(() => {
        // Auto grün, Fussgänger rot
        writeRegister(ADDR, REG_GPIO, 0b00001001);
        while (readRegister(ADDR, REG_GPIO) & 0x80) {
            ;
        }
        basic.pause(2000)
     
        // Auto gelb
        writeRegister(ADDR, REG_GPIO, 0b00001010);
        basic.pause(1000)
     
        // Alle rot
        writeRegister(ADDR, REG_GPIO, 0b00001100);
        basic.pause(1000)
     
        // Auto rot, Fussgänger grün
        writeRegister(ADDR, REG_GPIO, 0b00010100);
        basic.pause(5000)
     
        // Alle rot
        writeRegister(ADDR, REG_GPIO, 0b00001100);
        basic.pause(1000)
     
        // Auto gelb-rot
        writeRegister(ADDR, REG_GPIO, 0b00001110);
        basic.pause(1000)
    })
    function writeRegister(addr: number, reg: number, value: number) {
        pins.i2cWriteNumber(addr, reg * 256 + value, NumberFormat.UInt16BE)
    }
    function readRegister(addr: number, reg: number): number {
        pins.i2cWriteNumber(addr, reg, NumberFormat.Int8LE);
        return pins.i2cReadNumber(addr, NumberFormat.Int8LE)
    }
    Wetterdaten Eilenburg
    Temperatur: 23.2 °C
    Luftdruck: 1011.1 hPa
    Luftfeuchte: 38 %
    [mehr]