verschiedene i2c Geräte mit versch. Reg.-Adressen am selben Bus auslesen

hey,
wie kann ich verschiedene i2c Geräte mit verschiedenen device Adressen am selben Bus auslesen?
ich habe z.B.
0x02
0x03
0x15
0x41
0x42
0x43
0x55
0x71
0x72

alle müssen zu verschiedenen Zeiten in individuellen Abständen quasi asynchron bei Bedarf angesprochen werden (da verschiedene Prioritäten und nicht immer alles gleichermaßen interessant ist). Wie macht man das?

ps,
edit:
ganz vergessen:
Die haben ntl auch verschiedene Registeradressen, die teilweise vorher geschrieben werden müssen, bevor man sie (oder andere Register) auslesen kann.

So z.B.:

#define Echtzeituhr 0x68                                       // I2C-Adresse der RTC

void tempMessen()
{
  byte tempMSB, tempLSB, temp;
  Wire.beginTransmission(Echtzeituhr);  
  Wire.write(0x11);                                             // Registerzeiger auf erstes Temperaturregister stellen
  Wire.endTransmission();
   Wire.requestFrom(Echtzeituhr, 2);                    // Temperatur abfragen
  tempMSB = Wire.read();	
  tempLSB = Wire.read() >> 6;
  tempCelsius = tempMSB + (0.25*tempLSB);          // und ausrechnen
  Wire.beginTransmission(Echtzeituhr);
  Wire.write(0x00);                                            //Registerzeiger wieder auf Uhrregister stellen
  Wire.endTransmission();

Alles drin, da wird aus nem RTC-Modul die Temperatur ausgelesen, das hab ich früher mal so gemacht-inzwischen nutz ich allerdings nen besseren Temperatursensor für EinDraht-Bus.

Hallo,
häh! Das begreife ich nicht. Wenn Du zum auslesen eine bestimmte Adresse
verwendest, kannst Du doch lesen wann und wo Du willst.

Das kannst Du doch über eine Variable machen. Kein fertiger Code, aber so geht es...

int HYT939 = 0x28; //Start ADDR erster Sensor

void setup()
{
}

void loop()
{

 //HYT 939 Messwerte einmal holen

  if (zlHYT==0)
  {HYT939 = 0x28; // erster Sensor
zlHYT = 1;}
else 
  {HYT939 = 0x29; // zweiter Sensor
zlHYT = 0;}


//Messung starten


Wire.beginTransmission(HYT939); // transmit to device #44 (0x2c)
Wire.endTransmission(); // stop transmitting




//4 Bytes vom Sensor lesen

Wire.requestFrom(HYT939, 4,true);

i=0;

while(Wire.available()) {

char c = Wire.read(); // receive a byte as character

buffer[i]=c;

i++;

Das kann ich machen, wann und wo ich will.
Gruß und Spaß
Andreas

hmm - bisher musste ich
1.) erst gucken ob der i2c Bus frei ist
2.) wenn er frei ist, dann z.B. einen Array als i2c msg schicken mit verschiedenen Registeradressen und Datenarray, das dann vom Sensor gefüllt wird.
In einer C-ähnlichen Programmiersprache sah das so aus:

#define XGL_PORT S1
#define XGL_ADDR 0x02
#define XGL_DATA_REG  0x42
#define XGL_RESET_REG  0x60
#define XGL_ACC_SF_REG  0x61
#define XGL_DATA_PACKET 10
#define XGL_COMMAND 2

void XglReadPacket(XGLpacket &XglData)
{
	byte data[XGL_DATA_PACKET];
	byte cmd[XGL_COMMAND];
	byte count=XGL_DATA_PACKET;
	byte n_read=0;
	ArrayBuild(cmd,XGL_ADDR, XGL_DATA_REG);
	while (I2CStatus(XGL_PORT, n_read) ==  STAT_COMM_PENDING);
	if (I2CBytes(XGL_PORT, cmd, count, data))
	{//Assemble data
		XglData.mAng = data[0] + data[1]*256;
		XglData.mRate = data[2] + data[3]*256;
		XglData.mAccX = data[4] + data[5]*256;
		XglData.mAccY = data[6] + data[7]*256;
		XglData.mAccZ = data[8] + data[9]*256;
	}
}

Beschreibung:

The CruizCore® XG1300L I2C address is set to 0x02. Sensor data is stored internally in different registers 
that the NXT can access by sending read commands to the appropriate registers (see Table 1).
In addition to the register read operations, the CruizCore® XG1300L provides some additional
functions that can be accessed by sending write commands to specific registers as shown in Table
2. These functions allow resetting the sensor as well as changing the accelerometers scale factor.
[u]Table 1: The CruizCore® XG1300L internal registers[/u]
REGISTER (Hex value) Sensor Value
0x42 Accumulated Angle LSB
0x43 Accumulated Angle MSB
0x44 Rate of Turn LSB
0x45 Rate of Turn MSB
0x46 X-Accelerometer LSB
0x47 X-Accelerometer MSB
0x48 Y-Accelerometer LSB
0x49 Y-Accelerometer MSB
0x4A Z-Accelerometer LSB
0x4B Z-Accelerometer MSB
[u]Table 2: The CruizCore® XG1300L special function registers[/u]
REGISTER (Hex value) XG1300L ACTION
0x60 Reset device
0x61 Select ±2G accelerometer range
0x62 Select ±4G accelerometer range
0x63 Select ±8G accelerometer range

Kompletter Code:

/*
 * Microinfinity CO. LTD.
 * Test program for the XGL1300L device
 * This program displays the complete data package from the device
 * it resets the sensor when the user press the left button
 * it changes the accelerometer SF when the user press the right button
*/
//Define constants
#define XGL_PORT S1  // first of 4 NXT sensor ports
#define XGL_ADDR 0x02
#define XGL_DATA_REG  0x42
#define XGL_RESET_REG  0x60
#define XGL_ACC_SF_REG  0x61
#define XGL_DATA_PACKET 10
#define XGL_COMMAND 2
#define XGL_TIME_OUT 500

//XGL1300L sensor data packet
struct XGLpacket
{
	short mAng;
	short mRate;
	short mAccX;
	short mAccY;
	short mAccZ;
};

//Reset XGL1300L sensor
void XglReset()
{
	byte cmd[XGL_COMMAND];
	byte n_read=0;
	ArrayBuild(cmd,XGL_ADDR, XGL_RESET_REG);
	while (I2CStatus(XGL_PORT, n_read) ==  STAT_COMM_PENDING);
	I2CWrite(XGL_PORT,0,cmd);
}

//Change scale factor, the accSf parameter can be
// 0 -> +/- 2G
// 1 -> +/- 4G
// 2 -> +/- 8g
void XglAccSF(byte accSf)
{
	byte cmd[XGL_COMMAND];
	byte n_read=0;
	byte register= XGL_ACC_SF_REG+accSf;
	ArrayBuild(cmd,XGL_ADDR, register);
	while (I2CStatus(XGL_PORT, n_read) ==  STAT_COMM_PENDING);
	I2CWrite(XGL_PORT,0,cmd);
}

//Reads the full packet from XGL1300L

void XglReadPacket(XGLpacket &XglData)
{
	byte data[XGL_DATA_PACKET];
	byte cmd[XGL_COMMAND];
	byte count=XGL_DATA_PACKET;
	byte n_read=0;
	ArrayBuild(cmd,XGL_ADDR, XGL_DATA_REG);
	while (I2CStatus(XGL_PORT, n_read) ==  STAT_COMM_PENDING);
	if (I2CBytes(XGL_PORT, cmd, count, data))
	{//Assemble data
		XglData.mAng = data[0] + data[1]*256;
		XglData.mRate = data[2] + data[3]*256;
		XglData.mAccX = data[4] + data[5]*256;
		XglData.mAccY = data[6] + data[7]*256;
		XglData.mAccZ = data[8] + data[9]*256;
	}
}

task main()
{
	XGLpacket xgl;
	string msg;
	byte acc_sf=0;
	ReadButtonType rbArgs;

	//Initialize system
	SetSensorLowspeed(XGL_PORT);
	//Resets sensor and waits for hardware to settle
	XglReset();
	Wait(XGL_TIME_OUT);

	//Main loop
	while (1)
	{
		ClearScreen();
		XglReadPacket(xgl);
		TextOut(0, LCD_LINE1,"<RESET / ACC_SF>", false);
		//Print Angle  value
		TextOut(0, LCD_LINE3,"ANGLE:");NumOut(40, LCD_LINE3,xgl.mAng);
		//Print Rate value
		TextOut(0, LCD_LINE4,"RATE:");  NumOut(40, LCD_LINE4,xgl.mRate);
		//Print Acc x value
		TextOut(0, LCD_LINE5,"ACC_X:");  NumOut(40, LCD_LINE5,xgl.mAccX);
		//Print Acc y value
		TextOut(0, LCD_LINE6,"ACC_Y:");	NumOut(40, LCD_LINE6,xgl.mAccY);
		//Print Acc z value
		TextOut(0, LCD_LINE7,"ACC_Z:");	NumOut(40, LCD_LINE7,xgl.mAccZ);
		
		//Reset sensor if user press left key
		rbArgs.Index = BTNLEFT;
		SysReadButton(rbArgs);
		if (rbArgs.Pressed)
		{
			XglReset();
			TextOut(0, LCD_LINE2,"Reseting XGL ...", false);
			Wait(XGL_TIME_OUT);
		}

		//Change accelerometer SF if user press right key
		rbArgs.Index = BTNRIGHT;
		SysReadButton(rbArgs);
		if (rbArgs.Pressed)
		{
			acc_sf=(acc_sf==2)?0:acc_sf+1;
			XglAccSF(acc_sf);
			TextOut(0, LCD_LINE2,"Accel SF +/-");
			NumOut(75, LCD_LINE2,2<<acc_sf);
			Wait(XGL_TIME_OUT);
		}
		Wait(100);
	}
}

Unmittelbar danach musste ich zunächst warten, bis der i2c Bus frei ist, dann konnte ich den nächsten Sensor mit der nächsten dev addr an verschiedenen Registreradressen auslesen. Mal muss man dazu die Registeradressen mit angeben (z.B. MAX127), mal auch nicht (PCF8574).

ps,
in einer anderen, aber grundsätzlich ähnlichen PL sieht es auch deutlich komplizierter aus als in deinem obigen Arduino-Sketch-Beispiel:

/** \file microinfinity-cruizcore.h
* \brief MicroInfinity CruizCore XG1300L driver
*
* microinfinity-cruizcore.h provides an API for the MicroInfinity CruizCore XG1300L sensor.
*
* Changelog:
* - 0.1: Initial release
*
* Credits:
* - Big thanks to MicroInfinity for providing me with the hardware necessary to write and test this.
*
* License: You may use this code as you wish, provided you give credit where its due.
*
* THIS CODE WILL ONLY WORK WITH ROBOTC VERSION 4.10 AND HIGHER

* \author Xander Soldaat (xander_at_botbench.com)
* \date 29 May 2011
* \version 0.1
* \example microinfinity-cruizcore-test1.c
* \example microinfinity-cruizcore-test2.c
*/

#pragma systemFile

#ifndef __COMMON_H__
#include "common.h"
#endif

#define MICC_I2C_ADDR 0x02 /*!< MICC I2C device address */

#define MICC_ACC_ANG 0x42 /*!< MICC Accumulated angle (2 bytes) */

#define MICC_TURN_RATE 0x44 /*!< MICC Rate of Turn (2 bytes) */

#define MICC_X_ACCEL 0x46 /*!< MICC X acceleration data (2 bytes) */
#define MICC_Y_ACCEL 0x48 /*!< MICC Y acceleration data (2 bytes) */
#define MICC_Z_ACCEL 0x4A /*!< MICC Z acceleration data (2 bytes) */

#define MICC_CMD_RESET 0x60 /*!< MICC Reset the device */
#define MICC_CMD_RANGE_2G 0x61 /*!< MICC Acceleration up to 2G */
#define MICC_CMD_RANGE_4G 0x62 /*!< MICC Acceleration up to 4G */
#define MICC_CMD_RANGE_8G 0x63 /*!< MICC Acceleration up to 8G */

short MICCreadRelativeHeading(tSensors link);
short MICCreadTurnRate(tSensors link);
bool MICCreadAccel(tSensors link, short &x_accel, short &y_accel, short &z_accel);
bool MICCsendCmd(tSensors link, ubyte command);

#define MICCsetRange2G(x) MICCsendCmd(x, MICC_CMD_RANGE_2G) /*!< Macro for setting sensor to 2G range */
#define MICCsetRange4G(x) MICCsendCmd(x, MICC_CMD_RANGE_4G) /*!< Macro for setting sensor to 4G range */
#define MICCsetRange8G(x) MICCsendCmd(x, MICC_CMD_RANGE_8G) /*!< Macro for setting sensor to 8G range */
#define MICCreset(x) MICCsendCmd(x, MICC_CMD_RESET) /*!< Macro for resetting sensor */

tByteArray MICC_I2CRequest; /*!< Array to hold I2C command data */
tByteArray MICC_I2CReply; /*!< Array to hold I2C reply data */

/**
* Return the current relative heading, value between -179 and 180 degrees.

* Angle is measured in 100th degrees. So 12899 = 128.99 degrees.
* @return the relative heading
*/
short MICCreadRelativeHeading(tSensors link) {
  memset(MICC_I2CRequest, 0, sizeof(tByteArray));

  MICC_I2CRequest[0] = 2; // Number of bytes in I2C command
  MICC_I2CRequest[1] = MICC_I2C_ADDR; // I2C address of accel sensor
  MICC_I2CRequest[2] = MICC_ACC_ANG; // Set write address to sensor mode register

  if (!writeI2C(link, MICC_I2CRequest, MICC_I2CReply, 2))
    return 0;

  // Each result is made up of two bytes.
  return (MICC_I2CReply[1] << 8) + MICC_I2CReply[0];
}

/**
* Return the Rate of Turn in degrees per second
* @return the current rate of turn
*/
short MICCreadTurnRate(tSensors link) {
  memset(MICC_I2CRequest, 0, sizeof(tByteArray));

  MICC_I2CRequest[0] = 2; // Number of bytes in I2C command
  MICC_I2CRequest[1] = MICC_I2C_ADDR; // I2C address of accel sensor
  MICC_I2CRequest[2] = MICC_TURN_RATE; // Set write address to sensor mode register

  if (!writeI2C(link, MICC_I2CRequest, MICC_I2CReply, 2))
    return 0;

  // Each result is made up of two bytes.
  return (MICC_I2CReply[1] << 8) + MICC_I2CReply[0];
}

/**
* Read acceleration data from the sensor
* @param link the sensor port number
* @param x_accel X acceleration data
* @param y_accel Y acceleration data
* @param z_accel Z acceleration data
* @return true if no error occured, false if it did
*/
bool MICCreadAccel(tSensors link, short &x_accel, short &y_accel, short &z_accel) {
  memset(MICC_I2CRequest, 0, sizeof(tByteArray));

  MICC_I2CRequest[0] = 2; // Number of bytes in I2C command
  MICC_I2CRequest[1] = MICC_I2C_ADDR; // I2C address of accel sensor
  MICC_I2CRequest[2] = MICC_X_ACCEL; // Set write address to sensor mode register

  if (!writeI2C(link, MICC_I2CRequest, MICC_I2CReply, 6))
    return false;

  // Each result is made up of two bytes.
  x_accel = (MICC_I2CReply[1] << 8) + MICC_I2CReply[0];
  y_accel = (MICC_I2CReply[3] << 8) + MICC_I2CReply[2];
  z_accel = (MICC_I2CReply[5] << 8) + MICC_I2CReply[4];
  return true;
}

/**
* Send a command to the sensor
* @param link the sensor port number
* @param command the command to be sent
* @return true if no error occured, false if it did
*/
bool MICCsendCmd(tSensors link, ubyte command) {
  memset(MICC_I2CRequest, 0, sizeof(tByteArray));

  MICC_I2CRequest[0] = 2; // Number of bytes in I2C command
  MICC_I2CRequest[1] = MICC_I2C_ADDR; // I2C address of accel sensor
  MICC_I2CRequest[2] = command; // Set write address to sensor mode register

  return writeI2C(link, MICC_I2CRequest);
}

"Ähnliche" Programmiersprachen haben möglicherweise die nicht: http://arduino.cc/de/Reference/Wire

Rate mal, warum der I2C mein Lieblingsbus ist.. :wink:

Ein Master-Slave Protokoll ( wie I2C ) ist immer einfach, wenn es nur einen Master gibt, und der Master sich nicht selbst im Weg steht ( Preemptive Multitasking :fearful: ).

mal zurück zum topic:
mein Problem ist, dass ich
a) nicht weiß wie ich die Register einzeln auslesen kann und
b) verhindere, dass ich zu früh einen neuen Sensor anspreche, während der erste noch nicht alle Daten geschrieben oder zurückgeliefert hat (das ist aber vllt beim Arduino automatisch geregelt)

Also Hauptproblem sind die Register. Wie ihr seht, gibt es ja verschiedene Register für völlig verschiedene Daten, die ich nicht immer alle nacheinander brauche.

Wie hole ich also z.B. unmittelbar nacheinander
erst die Daten aus 0x42
und dann sofort danach aus 0x46
:?:

HaWe:
1.) erst gucken ob der i2c Bus frei ist

Wieso? Slaves können nie etwas unaufgefordert senden! Und auf dem Master kannst du sowieso nicht zwei Sachen gleichzeitig machen. Also weißt du immer wann du fertig bist.

ok, Datenstau scheint also kein Problem zu sein.

Also nochmal - Hauptproblem sind die Register. Wie ihr seht, gibt es ja verschiedene Register für völlig verschiedene Daten, die ich nicht immer alle nacheinander brauche.

Wie hole ich also z.B. unmittelbar nacheinander für das Gerät 0x02
erst die Daten aus Register 0x42 (2 Byte)
und dann sofort danach aus Register 0x46 (auch 2 Byte)
:?:

Das ist ganz, ganz einfach. Schau dir mal RTC Libs an. Da siehst du wie das gemacht wird. Vor allem die hier:
https://www.pjrc.com/teensy/td_libs_DS1307RTC.html

Mal vereinfacht ohne Fehlerbehandlung, geht das schreiben so:

Wire.beginTransmission(DEVICE_ADR);
Wire.write((byte)REGISTER); 

Wire.write(...);
Wire.write(...);

Wire.endTransmission();

Und das lesen so:

Wire.beginTransmission(DEVICE_ADR);
Wire.write((byte)REGISTER); 
Wire.endTransmission();

Wire.requestFrom(DEVICE_ADR, number_of_bytes);
byte b1 = Wire.read();
byte b2 = Wire.read();

Man sendet also die Device Adresse und dann das Start Register. Beim Schreiben kann man dann direkt weiteren Daten schreiben. Beim Lesen fordert man die Anzahl der zu lesenden Bytes an und liest dann.
Das geht natürlich auch in Schleifen, so dass man ein Array übergeben kann, dass dann gesendet oder beschrieben wird.

Die I2C Devices inkrementieren dann bei jedem read() oder write() ihren Adresszeiger, so das man bei sequentiellem Zugriff die Adresse nicht jedesmal schicken muss.

Wenn du verschiedene Register die nicht zusammenhängen auslesen musst, kann man natürlich einfach jedesmal eine anderes Start-Register setzen.

aha, ok, danke jetzt wird es klarer.
Wie ich Register 0x42 und 0x46 in 2 getrennten Befehlen nacheinander auslesen kann, ist mir jetzt klarer.
Kann ich sie aber auch unmittelbar nacheinander (ich meine damit: in einem einzigen Befehl gemeinsam) auslesen ?
Ich stelle mir vor, dass das ein Zeitvorteil wäre.

Du musst entweder den Adress-Zeiger neu setzen oder ein paar Dummy reads dazwischen machen um den Zeiger automatisch zu inkrementieren (und dabei auch mehr Daten anfordern, da read() die Daten nur aus dem Eingangspuffer des Arduino liest). Das kommt aufs Gleiche raus.

verstehe ich das richtig - diese folgende Methode ist also nicht deutlich langsamer als irgendeine andere...?

Wire.beginTransmission(0x02);
Wire.write((byte)0x42); 
Wire.endTransmission();

Wire.requestFrom(0x02, 2);
byte b1 = Wire.read();
byte b2 = Wire.read();

Wire.beginTransmission(0x02);
Wire.write((byte)0x46); 
Wire.endTransmission();

Wire.requestFrom(0x02, 2);
byte b1 = Wire.read();
byte b2 = Wire.read();

dann mach ich es nämlich genau so, das ist mir so am klarsten.

ps,
das andere habe ich jetzt glaube ich auch verstanden - als Reg 0x42 eingeben und die Länge auf 6 festlegen, dass ich dann gleich 3x2 Bytes auf einen Rutsch lese, inkl 2 Bytes von 0x44 und dann die 2 Bytes von 0x46, korrekt?

update - klappt soweit grundsätzlich, bin hier aber auf ein unerwartetes Problem gestoßen:
Ich muss offenbar die echten 8bit i2c dev Adressen 1x rechts shiften, dadurch bekommen die ungeraden GeräteAdressen die selbe wie die um 1 kleineren Nummern (42>>1 wird zu 21, 43>>1 ebenfalls 21).
Das ist ziemlich blöde, wenn das stimmt.

gibt's da nicht mal irgendwann ein fw/software-Update??

Ist doch ziemlich - nennen wir es höflich: - UNBEFRIEDIGEND, wenn die echten i2c-dev-Adressen nicht richtig vom Arduino gelesen werden (sondern nur ihr umgerechnet halbierter Wert), und dann dadurch auch noch die Hälfte aller möglichen (127) Firmen-definierten dev Adressen unbrauchbar ist!

Vermutlich verwirrt dich die 7 Bit Adresse des I2C Protokolls mit dem R/W Bit im LSB.
Dein "umgerechnet halbierter Wert" ist nun mal die Adresse.

Hier ist es recht deutlich beschrieben:

Nachtrag -kurzfassung- :

Some vendors incorrectly provide 8-bit addresses which include the read/write bit. Often, you can determine if this is the case because they will provide one address for writing to the slave device and another for reading from the slave. In this situation please only use the top seven bits of the address.

Wenn du denkst, die Hilfe der Library verwirrt dich mehr als dass es hilft, mach's eben ohne Library, schreib dir eine eigene (wo der Benutzer den Adress-shift implizit schon gemacht haben muss), oder gewöhne dich dran :wink:

Bei existierenden Libraries sollte jedenfalls nicht (und wird hier wohl nicht) das Verhalten und die Bedeutung von Parametern geändert werden.

hallo,
stimmt, das habe ich anders verstanden.
Aber ich habe z.B. 2x2 Geräte, jeweils völlig verschiedene Hersteller, u.a.:
devaddr 2 +3
devaddr 72+73
die sind so Hardwaremäßig festgelegt von den Herstellern und so kann ich sie auch von anderen Mastern aus ansprechen.
in Sketch muss ich aber >>1 shiften, um die oberen 7 bit zu lesen, erhalte also so 1 im oberen und 21 im unteren Fall.

Anders herum muss ich einen Arduino, der selber mit wire.begin(4) seine i2c-Adresse als 0x04 festgelegt hat, von anderen Geräten aus mit 4<<1 = 8 ansprechen.

D.h.: nicht der Arduino liest die "korrekte" Adresse, sondern nur einen Teil davon, nämlich die oberen 7 bit.

Hinweise wie "mach's eben ohne Library, schreib dir eine eigene (wo der Benutzer den Adress-shift implizit schon gemacht haben muss), oder gewöhne dich dran" kannst du allerdings gleich für dich behalten. Sie sind sinnlos und tragen nicht zur Problemlösung bei. Kann ja sein, dass ich hier einem Fehler aufgessen bin, aber bisher sieht es genau danach aus, wie ich es beschrieben habe.

Hallo,
"oder gewöhne dich dran" kannst du allerdings gleich für dich behalten."

Haalloo! Mache andere Leute doch nicht für Deine "Blö...." verantwortlich. Wenn Du nicht in der Lage bist "einfach" zu programmieren
dann liegt das ja wohl an Dir"
Ich habe hier einige I2C Sensoren am laufen, und es ist überhaupt kein Problem diese ohne Lib zu betreiben.
Gruß und Spaß
Andreas

die Tatsache, dass sogar die Arduino-selbstgewählte Slave-Adresse von Mastern mit <<1 geshiftet werden muss, und auch andere Slaveadressen andersherum >>1 für den Arduino zurückgeshiftet werden müssen zeigt doch, dass 8 bit-Adressen eher üblich sind als 7 bit.
Für diesen Blödsinn bin ja nun nicht ich verantwortlich.
Auf der anderen Seite komme ich ja gerne auch ohne die wire-lib aus, aber dann sag doch bitte auch netterweise, wie man das dann macht.