Himbeere als I2C-Sklave

9

Weiß jemand, wie man einen Raspberry Pi 3 als I2C-Slave (in C ++) einrichtet?

Bevor jemand antwortet, dass es nicht möglich ist, ist es möglich.

Hier ist ein Link, der besagt, dass es möglich ist (Pigpio-Dokumentation)

Dies ist der Code, den ich verwenden möchte (über den obigen Link):

#include <pigpio.h>
#include <iostream>
using namespace std;


int main(){
    bsc_xfer_t xfer;
    gpioInitialise();
    xfer.control = (0x0A<<16) | 0x305; // Set I2C slave Address to 0x0A
    int status = bscXfer(&xfer);
    if (status >= 0)
    {
        xfer.rxCnt = 0;
        while(1){
            if (xfer.rxCnt > 0){
                cout << xfer.rxBuf;
            }
       }    
    }

    return 1;
} 

Meine Verbindungen sind SCL zu BCM GPIO 18 und SDA zu BCM GPIO 19. (Dies sollte umgekehrt sein: SDA ist GPIO 18, SCL ist GPIO 19)

Die restlichen Geräte am Bus sind in Ordnung (bereits getestet).

Ich habe auch den SDA1 und den SCL1 der Himbeere an diesen I2C-Bus angeschlossen, damit ich i2cdetect -y 1I2C-Geräte im Bus auflisten kann .

Das Problem mit diesem Code ist, dass i2cdetect -y 1ich trotz korrekter Adressierung des Raspberry Pi die Adresse 0x0A sehen kann und keine Nachricht empfangen kann.

Wenn ich in einem anderen Prozess einen Sniffer ausführe, kann ich die Nachricht korrekt abrufen.

Sebastião
quelle

Antworten:

6

Dies ist fast identisch mit Ihrer vorherigen Frage. Sie hätten das wahrscheinlich bearbeiten sollen, anstatt eine neue Frage zu stellen.

Der bscXfer muss sich in der while-Schleife befinden. Auf diese Weise wird die xfer-Struktur mit neuen Informationen aktualisiert.

joan
quelle
Danke für die Antwort, es hat funktioniert! (Ich habe nicht genug Ruf, um eine +1 auf Ihre Antwort zu machen)
Sebastião
@ Sebastião Es ist gut zu wissen, dass es funktioniert hat. Ich finde normalerweise nur heraus, wenn etwas nicht funktioniert :(
Joan
Erwähnenswert ist auch, dass wir den Puffer nach dem Lesen löschen sollten, da er sonst Papierkorb aus älteren Nachrichten enthält. memset( xfer.rxBuf, '\0', sizeof(char)*BSC_FIFO_SIZE );werde es tun
Sebastião
Wissen Sie, wie Sie die I2C-Busgeschwindigkeit einstellen und welche ist die Standard- und Höchstgeschwindigkeit?
Sebastião
Die Standardgeschwindigkeit beträgt 100 kbps. Ich denke, die maximale Geschwindigkeit beträgt 3,8 Mps. Siehe (aus dem Speicher) / boot / Overlays / README für Details.
Joan
10

Da dieses Thema sehr schlecht behandelt wird und Sebastiãos Ausschnitt mir geholfen hat, dieses Problem zu lösen, möchte ich hier eine vollständige Lösung zum Einrichten eines RaspberryPi hinzufügen (getestet auf einem RPi 3 und Zero W)!

Einen funktionierenden Slave einrichten:

Vorbereitungen

Stellen Sie sicher, dass Sie diese Zeile in Ihrer Datei /boot/config.txt auskommentiert haben :

dtparam=i2c_arm=on

Abhängigkeiten

Als nächstes installieren Sie g ++ und pigpio mit diesem Befehl:

sudo apt install g++ pigpio

Stifte

Wie in der Dokumentation angegeben, sind die Pins GPIO 18 (SDA) und 19 (SCL) . Diese Seite hilft Ihnen beim Auffinden auf Ihrem RaspberryPi (das Layout sollte für RaspberryPi 2, 3, Zero und Zero W identisch sein).

Dieses Schema von der Website hilft: RaspberryPi GPIO Schema

Software

Erläuterung

Wie bereits erwähnt, basiert diese Lösung auf dem Code-Snippet von Sebastião . Ich habe es mit Hilfe von Joans Lösung modifiziert .

Ich habe auch versucht, den Code anhand der Dokumentation für die Funktion zu verstehen bscXfer.

Im Quellcode werden die Daten in der bsc_xfer_tStruktur zum Hinzufügen oder Empfangen von Nachrichten verwendet. Diese werden jedoch nur angewendet, wenn bscXfersie mit der Adresse der Struktur ausgeführt werden (wie Joan in seiner Lösung ausgeführt hat).

Die Ganzzahl bsc_xfer_t.control hat eine ganz besondere Rolle, die mehrere Dinge wie die Slave-I²C-Adresse und verschiedene andere Zustände angibt, die in der Dokumentation gut dokumentiert sind .

Um dies besser zu verstehen, habe ich die wichtigen Teile der Dokumentation in den Quellcode kopiert und einige Dinge geändert oder in separate Funktionen ausgelagert.

Quellcode

Die Adresse kann beliebig geändert werden (sofern sie nicht über 127 liegt (auch bekannt als 7F (16) oder 1111111 (2) ).

Da ich nicht gut in C ++ bin, müssen Sie auskommentieren, was Sie wollen, was Sie nicht tun wollen. Es wird empfohlen, die closeSlaveFunktion nach Abschluss des Tests auszuführen .

Hier die Datei slaTest.cpp :

#include <pigpio.h>
#include <iostream>

using namespace std;

void runSlave();
void closeSlave();
int getControlBits(int, bool);

const int slaveAddress = 0x03; // <-- Your address of choice
bsc_xfer_t xfer; // Struct to control data flow

int main(){
    // Chose one of those two lines (comment the other out):
    runSlave();
    //closeSlave();

    return 0;
}

void runSlave() {
    gpioInitialise();
    cout << "Initialized GPIOs\n";
    // Close old device (if any)
    xfer.control = getControlBits(slaveAddress, false); // To avoid conflicts when restarting
    bscXfer(&xfer);
    // Set I2C slave Address to 0x0A
    xfer.control = getControlBits(slaveAddress, true);
    int status = bscXfer(&xfer); // Should now be visible in I2C-Scanners

    if (status >= 0)
    {
        cout << "Opened slave\n";
        xfer.rxCnt = 0;
        while(1){
            bscXfer(&xfer);
            if(xfer.rxCnt > 0) {
                cout << "Received " << xfer.rxCnt << " bytes: ";
                for(int i = 0; i < xfer.rxCnt; i++)
                    cout << xfer.rxBuf[i];
                cout << "\n";
            }
            //if (xfer.rxCnt > 0){
            //    cout << xfer.rxBuf;
            //}
    }
    }else
        cout << "Failed to open slave!!!\n";
}

void closeSlave() {
    gpioInitialise();
    cout << "Initialized GPIOs\n";

    xfer.control = getControlBits(slaveAddress, false);
    bscXfer(&xfer);
    cout << "Closed slave.\n";

    gpioTerminate();
    cout << "Terminated GPIOs.\n";
}


int getControlBits(int address /* max 127 */, bool open) {
    /*
    Excerpt from http://abyz.me.uk/rpi/pigpio/cif.html#bscXfer regarding the control bits:

    22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
    a  a  a  a  a  a  a  -  -  IT HC TF IR RE TE BK EC ES PL PH I2 SP EN

    Bits 0-13 are copied unchanged to the BSC CR register. See pages 163-165 of the Broadcom 
    peripherals document for full details. 

    aaaaaaa defines the I2C slave address (only relevant in I2C mode)
    IT  invert transmit status flags
    HC  enable host control
    TF  enable test FIFO
    IR  invert receive status flags
    RE  enable receive
    TE  enable transmit
    BK  abort operation and clear FIFOs
    EC  send control register as first I2C byte
    ES  send status register as first I2C byte
    PL  set SPI polarity high
    PH  set SPI phase high
    I2  enable I2C mode
    SP  enable SPI mode
    EN  enable BSC peripheral
    */

    // Flags like this: 0b/*IT:*/0/*HC:*/0/*TF:*/0/*IR:*/0/*RE:*/0/*TE:*/0/*BK:*/0/*EC:*/0/*ES:*/0/*PL:*/0/*PH:*/0/*I2:*/0/*SP:*/0/*EN:*/0;

    int flags;
    if(open)
        flags = /*RE:*/ (1 << 9) | /*TE:*/ (1 << 8) | /*I2:*/ (1 << 2) | /*EN:*/ (1 << 0);
    else // Close/Abort
        flags = /*BK:*/ (1 << 7) | /*I2:*/ (0 << 2) | /*EN:*/ (0 << 0);

    return (address << 16 /*= to the start of significant bits*/) | flags;
}

Beachten Sie, dass in einigen Fällen das erste Byte das Befehlsbyte und nicht Teil Ihrer allgemeinen Daten sein soll.

BEARBEITEN: Beachten Sie auch, dass dies zwar zu Testzwecken gut funktioniert, @crasic jedoch (erster Kommentar) darauf hinwies, dass es eine bessere (aber auch schlecht dokumentierte) Möglichkeit gibt, dies mit Ereignissen zu tun, anstatt eine Endlosschleife zu verwenden. Das sollte besser sein, wenn es mit mehreren Anwendungen verwendet wird.

Kompilieren und ausführen

Sie können dies mit kompilieren

g++ slaveTest.cpp -lpthread -lpigpio -o slaveTest

und ausführen mit

sudo ./slaveTest

Test mit einem Meister

Um es kurz als Master zu testen, ist die Verwendung von smbus eine beliebte Option, die viel einfacher ist und durch einfaches Suchen mit einer Suchmaschine Ihrer Wahl gefunden werden kann.

Meine gewählte Option in Kürze:

  • Schlagen Sie die SDA- und SCL-Pins nach (sie unterscheiden sich als Master!)
  • Lauf sudo apt install python3 python3-smbus
  • Kopieren Sie das folgende Snippet als zB masterI2C.py auf Ihr RPi
  • Öffnen Sie mit diesem Snippet eine Python-Shell python3 -i masterI2C.py
  • Ausführen sendData(0x03, 'Hello World of I2C!'), um Daten zu senden

Master-Python-Snippet:

import smbus

bus = smbus.SMBus(1)

def sendData(slaveAddress, data):
    intsOfData = list(map(ord, data))
    bus.write_i2c_block_data(slaveAddress, intsOfData[0], intsOfData[1:])

Bild des Testens:

Teste es selbst


Ich hoffe, dass ich dieses Thema für andere Menschen klären konnte.

(Bei plötzlichen Problemen half mir normalerweise ein Neustart meines Sklaven-Himbeer-Pi.)

LinusCDE98
quelle
2
Gute Antwort. Zwei Vorschläge zur akademischen Vollständigkeit. Die Verwendung einer while-Schleife ist für eine geringe Rechenlast und langsame Kommunikation in Ordnung und repliziert, was bei vielen einfachen Mikrocontrollern der Fall ist. Ein besserer Weg ist jedoch die Ereignissteuerung durch Verwendung eines Interrupts auf dem TxStatus. Dies ist in BCM schlecht dokumentiert, aber verfügbar. # 2 Ebenso schlecht dokumentiert ist die Verwendung von DMA. Wenn Sie an der Entwicklung einer vollständigen I2C-Slave-Lösung interessiert sind, die sich gut für andere Anwendungen auf dem Linux-System eignet, ist dies der nächste Ort, an dem Sie mit dem Graben beginnen können.
krass
1
@crasic Das ist schön zu hören. Da ich mich (momentan) nicht für C ++ interessiere, weiß ich nicht wirklich, wie ich das machen soll. Aber ich werde eine Notiz in der Lösung für andere Leute hinterlassen, die wissen, wie man es richtig macht.
LinusCDE98