Wie Lib ändern um SDA/SCL pins zu übergeben

Hallo miteinander, ich mal wieder mit nem Problemchen.
Ich habe 2 M5 Hat_8servos geholt, das die Servos über I2C anspricht. Ich will die Dinger jedoch nicht mit dem M5 sondernmit nem STM32 nutzen. das problem ist, die Hat's haben leider eine fixe, nicht änderbare I2C-Adresse (0x36) dan mein STM32 Board (ein Stm32F411 blackpill) mehrere I2C-Schnittstellen besitzt will ich jeweils ein Hat an I2C 1 und das andere an I2C 2 hängen. In der Lib zum Hat sind jedoch die I2C Pins in der .h datei direkt eingetragen. Ich möchte jetzt jedoch gerne beim initialisieren der Lib also da wo ich angebe 'Hat_8Servos myServo' in klamern auch die SDA/SDL Pins mit übergeben, so dass ich die Lib mit meinem Conroller nutzen kann und auch die zu benutzende Schnittstelle durch die Angabe der Pins wählen kann. Wie muss ich die Hat_8Servos.h ändern, damit ich die Pins beim Init angeben kann?
hier die Hat_8Servos.h;


/*!
 * @brief  An 8-channel servo driver module that works From M5Stack
 * @copyright Copyright (c) 2022 by M5Stack[https://m5stack.com]
 *
 * @Links [8Servos HAT v1.1](https://docs.m5stack.com/en/hat/hat_8servos_1.1)
 * @version  V0.0.2
 * @date  2022-07-25
 */
#ifndef _HAT_8SERVOS_H_
#define _HAT_8SERVOS_H_

#include <Arduino.h>
#include <Wire.h>

#define SERVOS_DEVICE_ADDR 0x36
#define SERVOS_ANGLE_REG   0x00
#define SERVOS_PULSE_REG   0x10
#define SERVOS_MOS_CTL_REG 0x30

class Hat_8Servos {
   private:
    uint8_t _addr;
    TwoWire *_wire;
    uint8_t _sda;
    uint8_t _scl;
    bool writeBytes(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_t length);
    bool readBytes(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_t length);

   public:
    bool begin(TwoWire *wire = &Wire, uint8_t sda = 0, uint8_t scl = 26,
               uint8_t addr = SERVOS_DEVICE_ADDR);
    bool setServoAngle(uint8_t index, uint8_t deg);
    bool setAllServoAngle(uint8_t deg);
    bool setServoPulse(uint8_t index, uint16_t pulse);
    bool setAllServoPulse(uint16_t pulse);
    bool enableServoPower(uint8_t state);
    uint8_t getServoAngle(uint8_t index);
    uint16_t getServoPulse(uint8_t index);
};

#endif

hier die Hat_8Servos.cpp

#include "Hat_8Servos.h"

/*! @brief Initialize the hardware.
    @return True if the init was successful, otherwise false.. */
bool Hat_8Servos::begin(TwoWire *wire, uint8_t sda, uint8_t scl, uint8_t addr) {
    _wire = wire;
    _addr = addr;
    _sda  = sda;
    _scl  = scl;
    _wire->begin(_sda, _scl);
    delay(10);
    _wire->beginTransmission(_addr);
    uint8_t error = _wire->endTransmission();
    if (error == 0) {
        return true;
    } else {
        return false;
    }
}

/*! @brief Write a certain length of data to the specified register address.
    @return True if the write was successful, otherwise false.. */
bool Hat_8Servos::writeBytes(uint8_t addr, uint8_t reg, uint8_t *buffer,
                             uint8_t length) {
    _wire->beginTransmission(addr);
    _wire->write(reg);
    _wire->write(buffer, length);
    if (_wire->endTransmission() == 0) return true;
    return false;
}

/*! @brief Read a certain length of data to the specified register address.
    @return True if the read was successful, otherwise false.. */
bool Hat_8Servos::readBytes(uint8_t addr, uint8_t reg, uint8_t *buffer,
                            uint8_t length) {
    uint8_t index = 0;
    _wire->beginTransmission(addr);
    _wire->write(reg);
    _wire->endTransmission();
    if (_wire->requestFrom(addr, length)) {
        for (uint8_t i = 0; i < length; i++) {
            buffer[index++] = _wire->read();
        }
        return true;
    }
    return false;
}

/*! @brief Set the angle of a certain servo.
    @return True if the set was successful, otherwise false.. */
bool Hat_8Servos::setServoAngle(uint8_t index, uint8_t deg) {
    uint8_t data = deg;
    uint8_t reg  = index;
    return writeBytes(_addr, reg, &data, 1);
}

/*! @brief Set the angle of all servos.
    @return True if the set was successful, otherwise false.. */
bool Hat_8Servos::setAllServoAngle(uint8_t deg) {
    uint8_t data[8] = {0};
    memset(data, deg, 8);
    return writeBytes(_addr, SERVOS_ANGLE_REG, data, 8);
}

/*! @brief Set the pulse of a certain servo.
    @return True if the set was successful, otherwise false.. */
bool Hat_8Servos::setServoPulse(uint8_t index, uint16_t pulse) {
    uint8_t data[2] = {0};
    data[1]         = pulse & 0xff;
    data[0]         = (pulse >> 8) & 0xff;
    uint8_t reg     = SERVOS_PULSE_REG + (2 * index);
    return writeBytes(_addr, reg, data, 2);
    ;
}

/*! @brief Set the pulse of all servos.
    @return True if the set was successful, otherwise false.. */
bool Hat_8Servos::setAllServoPulse(uint16_t pulse) {
    for (uint8_t i = 0; i < 8; i++) {
        if (!setServoPulse(i, pulse)) {
            return false;
        }
    }
    return true;
}

/*! @brief Turn on power to all servos.
    @return True if the set was successful, otherwise false.. */
bool Hat_8Servos::enableServoPower(uint8_t state) {
    uint8_t data = state;
    return writeBytes(_addr, SERVOS_MOS_CTL_REG, &data, 1);
}

/*! @brief Get the angle of a particular servo.
    @return Angle of a certain servo.. */
uint8_t Hat_8Servos::getServoAngle(uint8_t index) {
    uint8_t data = 0;
    uint8_t reg  = SERVOS_ANGLE_REG + index;
    if (readBytes(_addr, reg, &data, 1)) {
        return data;
    } else {
        return 0;
    };
}

/*! @brief Get the angle of a particular pulse.
    @return Pulse of a certain servo.. */
uint16_t Hat_8Servos::getServoPulse(uint8_t index) {
    uint8_t data[2] = {0};
    uint8_t reg     = SERVOS_PULSE_REG + index;
    if (readBytes(_addr, reg, data, 2)) {
        return (data[1] << 8) | data[0];
    } else {
        return 0;
    };
}

Danke schon im voraus für eure Hilfe.

--> poste Code immer in Code Tags - ändere das nachträglich.
--> lade die ganze lib hier hoch, ich hatte jetzt zwei mal 404er Fehler auf der Servo Seite, es langt.
--> Oder verlinke es so, dass man sich das ganze zip ansehen kann.
--> mach mal einen Beispiel-Sketch für deinen STM wo du beide I2C Schnittstellen anlegst.

Musst Du nicht.
Im Example M5Hat_8Servos ist für eine Instanz im setup ausgeschrieben:

drive.begin(&Wire, 0, 26, 0x36) // referenz auf wire, SDA, SCL, ADRESSE

Du musst jetzt nur vorher eine zweite Instanz festlegen

Hat_8Servos onedrive;
Hat_8Servos twodrive;

void setup()
{
onedrive.begin(&Wire, B7, B6, 0x36)
twodrive.begin(&Wire, B3, B10, 0x36)
}

Das wäre mein Ansatz.

Überhaupt nicht, allerdings werden SDA und SCL mit der Methode begin festgelegt:

bool begin(TwoWire *wire = &Wire, uint8_t sda = 0, uint8_t scl = 26,
uint8_t addr = SERVOS_DEVICE_ADDR);

Im Programm dann (ungetestet):

Hat_8Servos drive_1;
Hat_8Servos drive_2;
...
void setup() {
...
  drive_1.begin(&Wire, SDA_1, SCL_1, 0x36)
  drive_2.begin(&Wire, SDA_2, SCL_2, 0x36)

Möglicherweise
M5Hat-8Servos-master.zip (78,9 KB)

Upps, wieder nur zu langsam :stuck_out_tongue_winking_eye:

imho braucht es vorab schon zwei getrennte Wire Instanzen...
besonders weil jede M5Hat Instanz dann doch jeweils ein wire.begin macht und das stelle ich mir auf einer Wire Instanz doch eher suboptimal vor.

Wobei ich mich frage, wie das weitergeht. Eine zweite Instanz muß ja nicht nur die SCL/SDA Pins kennen, sondern auch die Register oder speziellen Methoden zum Ansprechen der richtigen I2C Hardware.

Oder handelt es sich hier um ein Soft-I2C? Dann würde ich dem überhaupt nicht über den Weg trauen!

Das ist mit Sicherheit nicht trivial!
Auf die Schnelle habe ich auch nix dazu gefunden!

Eine Alternative?
Ein PCA9685 kann 16 Servos bedienen.

Noch eine Alternative:
Ein I2C Multiplexer, z.B. PCA9548A

Da sehe ich hingegen weniger Probleme.

Softwire ist auch eine Alternative Möglichkeit.

Hallo,

du programmierst den STM32 doch sicherlich mit der Arduino IDE wie man erkennen kann. Schau mal in die IDE Board Einstellungen was du alles für den Controller konfigurieren kannst. Wenn der Package Ersteller mitgedacht und alles vorgesehen hast, wäre es möglich das man hier mehrere Wire Schnittstellen aktivieren kann. In meinem Fall kann ich für meinen AVRxDB beide Wire Schnittstellen konfigurieren. Am Besten noch die liest die Beschreibung zum STM Package. Ich gehe davon aus es gibt eine halbwegs vernünftige Doku zu deinem Package. Dann kannst du das Wire Objekt deiner Klasse/Instanz/Funktion oder was auch immer übergeben.

Als naiver Mensch habe ich mal eine Methode geändert:

uint8_t Hat_8Servos::getServoAngle(uint8_t index) {
	return _scl;
}

und auf einem ESP32 probiert:

#include "Hat_8Servos.h"
Hat_8Servos drive_1;
Hat_8Servos drive_2;

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println("\nStart ...");
  drive_1.begin(&Wire, 21, 22, 0x36);
  drive_2.begin(&Wire, 16, 17, 0x36);
  Serial.println(String(drive_1.getServoAngle(0)));
  Serial.println(String(drive_2.getServoAngle(0)));
}
void loop() {}

Ausgabe:

Start ...
22
17

Nach meinem Eindruck werden in der Bibliothek außer den Pins kaum Informationen gehalten. Hätte ich einen STM32 und so ein Servo, würde ich meinem naiven Versuch eine Chance geben :crazy_face:

das die member Variable _scl den Wert halten kann ist schon klar,
wohin ein späteres _wire-->transmit die Daten sendet steht auf einem anderen Blatt.

ich behaupte mal befehle für drive_1 gehen schief wenn man sich nicht um eine zweite Wire Instanz kümmert.

Das mit den Codetags habe ich versucht, aber wohl falsch gemacht - ich war aus gesundheitlichen gründen sehr sehr lange nicht mehr programiertechnisch unteregs. Sorry, ich schaue ob ich das mit dem Code noch korrigieren kann.

Ganz einfach
```
Dein Programm
```

oder

<code>
Dein Programm
</code>

das oder den Button </> verwenden. Mit den Code Tags hatte ich mal ein Problem, da wurde was falsch formatiert. Warum auch immer. Aber irgendwas war da gewesen wenn ich mich dunkel erinnere. Ich glaube das war im Modellbahn Thread ... damals. In der Regel funktionieren aber beide Methoden.

ja ich errinnere mich, früher hatte ich jeweils einfach die codetags geschrieben, und dann ab der neuen Zeile meinen code eingefügt. im neuen Forum klappt das iwie nicht mehr richtig. Aber ich habe mein Eingangspost jetzt editiert und nun sollte das passen.
@my_xy_projekt : Wo hast Du das M5Hat_8Servos Example gefunden? der Lib liegen nur 2 Samples bei, die beide nur mit Hat_8Servos beginnen. OK da ich kein M5 MCU Board habe, sondern nur die Hat's , habe ich die M5 boards auch nicht in der IDE installiert. Ich wollte einfach nur diese I2C Servo-Hats mit meinem STM32 Blackpill-Board betreiben.
Ich habe , nachdem ich mir die lib angeschaut habe, in den Exampels nach einem drive.begin gesucht, da aber keines gefunden. Und mich hat dann die Feste zuweisung der SDA/SCL Pins etwas verwirrt.
@all ; was die nutzung der 2. I2c-Schnittstelle anbelangt, da hat im englischsprachigen Teil des Arduinoforums jemand bei einem STM32 - board, da allerdings dem BluePill eine lcd-Lib umgestrickt, um ein LCD auf der 2. I2c laufen zu lassen, auch durch angabe der Pins. Und da der stm32 Controler 2 Hardware I2c hat (eigentlich sogar 3) dachte ich, ich könnte doch einfach jedes der Hats an eine eigene Schnittstelle hängen, um das problem der fixen Adresse zu umgehen. Und ein 16-er Servocontroller kommt nicht in frage, da ich dann auch wieder 2 davon bräuchte, da die beiden hats in 2 unterschiedlichen gehäusen untergebracht werden und ich nicht mit so vielen Servokabeln von einem Gehäuse zum nächsten will, auch wenn die Distanz Zwischen den Gehäusen nicht sehr gross ist. Dazu haben die Hat's gleich auch noch ne batterie oder nen Akku drin , den ich zur Versorgung nutzen kann.

Das mit dem drive.begin werde ich selbstverständlich ausprobieren, denn genau danach hatte ich ja ursprünglich, bevor ich an Euch rangetreten bin, gesucht. Ich hätte wohl besser doch auch die M5 Boards mit installieren sollen.

du drehst dich im Kreis.
Die HAT Lib ist bereit vorbereitet dem Objekt eine Wire Schnittstelle zu übergeben.
Da brauchst nichts mehr ändern. Das ist so vorgesehen.

Wenn du Vergleiche zu LCD anstellst, nur wenige LCD-Libs sind derartig vorbereitet - daher muss man dann die LCD lib ändern. (nur der Vollständigkeit - meine LCD Lib kanns out of the box).

Also ... such dir jetzt endlich ein Beispiel wie man die zwei Wire (oder 3) Wire Schnittstellen auf deinem STM nutzt ... das ist der Schlüssel zu deinem Problem.

An der Lib änderst vorerst mal gar nichts.

Oh, Du hast Deinen Beitrag geqrasht;

Wenn Du Formatierungszeichen darstellen willst, muss ein \ davor gesetzt werden.
Ich hab das in dem Zitat korrigiert. Sieht dann so aus:
-> einfach \' geschrieben\`,

Das Example ist mit bei.
Im ersten, Hat_8Servos1.1_M5StickC.ino steckt drin:

while (!drive.begin(&Wire, 0, 26, 0x36)) {
        canvas.drawString("Connect Error", 40, 80);
        canvas.pushSprite(0, 0);
        vTaskDelay(100);
    }

Die Methode selbst ist im Hat_8Servos.cpp gleich die erste.
Darum bin ich auch davon ausgegangen, das mehrfache verwenden möglich ist, da für jede Methode die Pinkonfig gesetzt wird.

bool Hat_8Servos::begin(TwoWire *wire, uint8_t sda, uint8_t scl, uint8_t addr) {
    _wire = wire;
    _addr = addr;
    _sda  = sda;
    _scl  = scl;
    _wire->begin(_sda, _scl);
    delay(10);
    _wire->beginTransmission(_addr);
    uint8_t error = _wire->endTransmission();
    if (error == 0) {
        return true;
    } else {
        return false;
    }
}

Ja , jetzt wo Du und die andern mir so schön auf die Sprünge geholfen habt, werde ich das selbstverständlich tun.

@noiasca : nein ich habe das inzwischen verstanden, ich will nix mehr an der lib ändern.
@my_xy_projekt : bin ich echt sooo blind? Bitte bitte hau mir doch mal kurz aber ganz kräftig auf den Hinterkopf, vielleicht kommt dann mein Denkvermögen wieder in Gang.
Sorry dass ich so begriffsstutzig bin, mein Kopf und denkvermögen ist dutch die lange krankheit echt in Mitleidenschaft gezogen worden und das Programmieren fällt mir momentan unheimlich schwer - dennoch will ich dieses Projekt unter Dach und Fach bringen, dank eurer Hilfe komme ich jetzt weiter.
Hezlichen Dank für eure wertvolle Hilfe.

Der kann das ganz gut.
:judge:

Dankeschön, das hab ich jetzt gebraucht. :upside_down_face: