Forum: Mikrocontroller und Digitale Elektronik STM32 I2C Probleme


von Killerawft (Gast)


Lesenswert?

Hallo.

Ich versuche mich gerade daran mit dem STM32F3 Discovery Board meinen 
ersten 32-Bit ARM zu programmieren. Bis jetzt habe ich erfolgreich die 
onboard LED's mit einem Timer blinken lassen. jetzt möchte ich als 
Vorbereitung auf mein nächstes Projekt den I2C Bus verwenden. Als 
Einstieg hab ich drei LED's an den MCP23017 Port Expander gehängt und 
möchte die zum leuchten bringen.
Allerdrings scheitert es wohl schon am richtigen Initialisieren vom I2C 
Bus.
Ich möchte den I2C_2 Bus nehmen an PinA09 und PinA10. Ich habe diverse 
Codeschnipsel für die STM32F Serie im Internet gefunden und meiner 
Meinung nach müsste ich zumindest am Clock Pin den Takt messen können, 
jedoch zeigt mein Oszilliskop nur dauerhaft 3 Volt an und das Programm 
bleibt beim Debuggen bei der Überprüfung des transmit interrupted flags 
hängen. Auch am Datenpin messe ich nur konstante 3V von den 1kOhm 
Pull-Up Widerständen.

Hier ist mein Code
1
int main(void)
2
{
3
4
  
5
        SystemInit(); //System Clock auf Extern, also 72MHz einstellen
6
  InitClock();
7
  InitGPIOE();
8
  InitGPIOA();
9
  InitTimer2();
10
  InitI2C_2();
11
12
  GPIOE->BSRR = 0xFFFF0000; //Alle LED's ausschalten
13
14
    while(1) // Endlos SChleife
15
    {
16
      I2C_WrReg(GPIOB_MCP,0x01);
17
      Delay(1000000);
18
      I2C_WrReg(GPIOB_MCP,0x00);
19
    }
20
}
21
22
void InitI2C_2(void)
23
{
24
  GPIO_InitTypeDef GPIO_InitStruct;
25
  I2C_InitTypeDef I2C_InitStruct;
26
27
  RCC_I2CCLKConfig(RCC_I2C2CLK_SYSCLK);
28
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
29
30
  // configure I2C2
31
  I2C_DeInit(I2C2);
32
  I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // I2C mode
33
  I2C_InitStruct.I2C_AnalogFilter = I2C_AnalogFilter_Enable;
34
  I2C_InitStruct.I2C_DigitalFilter = 0x00;
35
  I2C_InitStruct.I2C_OwnAddress1 = 0x00; 
36
  I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; 
37
  I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
38
  I2C_InitStruct.I2C_Timing = 0x00201D2B; 
39
  I2C_Init(I2C2, &I2C_InitStruct); // init I2C2
40
41
  // enable I2C2
42
  I2C_Init(I2C2, &I2C_InitStruct);
43
  I2C_Cmd(I2C2, ENABLE);
44
45
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
46
47
  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
48
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
49
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_1;
50
  GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
51
  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; 
52
  GPIO_Init(GPIOA, &GPIO_InitStruct);
53
54
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_4); // SCL
55
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_4); // SDA
56
57
}
58
59
60
61
void I2C_WrReg(uint8_t Reg, uint8_t Val){
62
63
  //Wait until I2C isn't busy
64
  while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY) == SET);
65
66
  I2C_TransferHandling(I2C2, PE_Addr, 1, I2C_Reload_Mode,I2C_Generate_Start_Write);
67
68
  //Ensure the transmit interrupted flag is set
69
  while(I2C_GetFlagStatus(I2C2,I2C_FLAG_TXIS) == RESET); //!!HIER BLEIBT DAS PROGRAMM STEHEN
70
71
  //Send the address of the register we wish to write to
72
  I2C_SendData(I2C2, Reg);
73
74
  while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TC) == RESET);
75
76
  I2C_TransferHandling(I2C2, PE_Addr, 1, I2C_AutoEnd_Mode, I2C_No_StartStop);
77
78
  while(I2C_GetFlagStatus(I2C2, I2C_FLAG_TXE) == RESET);
79
80
  I2C_SendData(I2C2, Val);
81
82
  while(I2C_GetFlagStatus(I2C2, I2C_FLAG_STOPF) == RESET);
83
84
  //Clear the stop flag for the next potential transfer
85
  I2C_ClearFlag(I2C2, I2C_FLAG_STOPF);
86
}

Vielleicht findet ihr ja einen Fehler in meinem Programm oder habt Tips, 
wo ich mich nochmal umschauen kann nach einer Lösung.

Killerawft

von Joe F. (easylife)


Lesenswert?

Killerawft schrieb:
> 3 Volt

3 oder 3.3V?

von Killerawft (Gast)


Lesenswert?

Ich Messe am IC ca. 2,95V benutze aber zur Spannungsversorgung vom IC 
die 3V output von dem Board und das BOard ist üer den ST-Link USB am PC 
angeschlossen. Das sollte eigentlich ja passen.

von Killerawft (Gast)


Lesenswert?

Hat denn keiner Erfahrung damit und könnte mir einen Tipp geben?

von hp-freund (Gast)


Lesenswert?

Vielleicht hast Du die erste Ausgabe verpasst und dann:

Delay(1000000);

musst Du 1000 Sekunden auf die nächste warten ?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Auf dem F4 Disco Board initialisiere ich zuerst den Port und dann das 
I²C Interface, das klappt so. Evtl. (ich habs andersrum nie ausprobiert) 
solltest du das auch so machen.
Ausserdem fehlt mir in deiner Schreibroutine das Erzeugen der Stop 
Bedingung(?). Hier mal ein Stückchen Code für I2C1 auf dem F4, 
ursprünglich von Uwe B:
1
/* Write a data to a I2C subregister  */
2
int16_t I2C_WriteByte(uint8_t slave_adr, uint8_t adr, uint8_t wert)
3
{
4
  uint32_t timeout=I2C1_TIMEOUT;
5
6
  // init start
7
  I2C_GenerateSTART(I2C1, ENABLE);
8
9
  timeout=I2C1_TIMEOUT;
10
  while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB)) {
11
    if(timeout!=0) timeout--; else return(P_I2C1_timeout(-1));
12
  }
13
14
  // send master adress for writing
15
  I2C_Send7bitAddress(I2C1, slave_adr, I2C_Direction_Transmitter);
16
17
  timeout=I2C1_TIMEOUT;
18
  while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR)) {
19
    if(timeout!=0) timeout--; else return(P_I2C1_timeout(-2));
20
  }
21
  // clr addr flag
22
  I2C1->SR2;
23
24
  timeout=I2C1_TIMEOUT;
25
  while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) {
26
    if(timeout!=0) timeout--; else return(P_I2C1_timeout(-3));
27
  }
28
  // send subadress
29
  I2C_SendData(I2C1, adr);
30
  timeout=I2C1_TIMEOUT;
31
  while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) {
32
    if(timeout!=0) timeout--; else return(P_I2C1_timeout(-4));
33
  }
34
  // send data
35
  I2C_SendData(I2C1, wert);
36
  timeout=I2C1_TIMEOUT;
37
  while ((!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) || (!I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF))) {
38
    if(timeout!=0) timeout--; else return(P_I2C1_timeout(-5));
39
  }
40
  // finish I2C transaction
41
  I2C_GenerateSTOP(I2C1, ENABLE);
42
  Delay(2);
43
  return 0;
44
}
Du siehst, das der grösste Aufwand zur Vermeidung von ewigen Hängern 
getrieben wurde.

: Bearbeitet durch User
von Killerawft (Gast)


Lesenswert?

Entschuldigung, dass ich mich erst so spät wieder melde. Ich hatte die 
Tage nicht viel Zeit zum programmieren und werde wohl die nächsten Tage 
auch nicht dazu kommen.

Wie dem auch sei, erstmal zu hp-freund. Die delayzeit sind takte und 
keine mS. Der delay ist ca 1 Sekunde und ich hab mit der Zahl auch schon 
LEDs im 1 Sekunden Takt bekommen blinken lassen.

Dann zu Matthias Sch. Ich muss bei dem Code erstmal einige Flags 
umbenennen, da die bei dem F3 anders heißen und danach kann ich sagen ob 
mir das geholfen hat, aber trotzdem schon mal danke, dass du den Code 
hier gepostet hast.

Ich hab auch nochmal eine allgemeine Frage. Muss nicht sobald ich den 
I2C initialisiert habe beim SCL pin der 100kHz Takt Anliegen? Ich messe 
da zumindest vom Start des Controllers bis zu dem punkt, an dem er fest 
hängt keinen Takt.

von Killerawft (Gast)


Lesenswert?

Guten Tag.

Ich hab das Problem gerade selbst mit Hilfe des I2C Examples von der 
CooCox IDE, die ich benutze, lösen können.
Das Problem war, dass bei dem STM32F3 nicht die I2C_Flag_... sondern 
I2C_ISR_... Flags abgefragt werden müssen.

Hier für alle, die durch fleißiges Googeln hier drauf stoßen nochmal 
mein funktionierender Code.
1
void InitI2C_2(void)
2
{
3
4
5
  GPIO_InitTypeDef GPIO_InitStruct;
6
  I2C_InitTypeDef I2C_InitStruct;
7
8
  RCC_I2CCLKConfig(RCC_I2C2CLK_SYSCLK);
9
10
11
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
12
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
13
14
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_4); // SCL PA9
15
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_4); // SDA PA10
16
17
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
18
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
19
  GPIO_InitStruct.GPIO_OType = GPIO_OType_OD; // set output to open drain --> the line has to be only pulled low, not driven high
20
  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // enable pull up resistors
21
22
  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //SDA
23
  GPIO_Init(GPIOA, &GPIO_InitStruct);
24
25
  // configure I2C2
26
  I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // I2C mode
27
  I2C_InitStruct.I2C_AnalogFilter = I2C_AnalogFilter_Enable;
28
  I2C_InitStruct.I2C_DigitalFilter = 0x00;
29
  I2C_InitStruct.I2C_OwnAddress1 = 0x00; // own address, not relevant in master mode
30
  I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // disable acknowledge when reading (can be changed later on)
31
  I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // set address length to 7 bit addresses
32
  I2C_InitStruct.I2C_Timing = 0xC062121F; // 100kHz  0x10805E89    Meins: 0x00201D2B
33
  I2C_Init(I2C2, &I2C_InitStruct); // init I2C2
34
35
  // enable I2C2
36
  I2C_Cmd(I2C2, ENABLE);
37
38
}
39
40
41
void I2C_WrReg(uint8_t Reg, uint8_t Val){
42
43
  //Wait until I2C isn't busy
44
  while(I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY) == SET);
45
46
  I2C_TransferHandling(I2C2, PE_Addr, 1, I2C_Reload_Mode, I2C_Generate_Start_Write);
47
48
  //Ensure the transmit interrupted flag is set
49
  while(I2C_GetFlagStatus(I2C2,I2C_ISR_TXIS) == RESET);
50
51
  //Send the address of the register we wish to write to
52
  I2C_SendData(I2C2, (uint8_t)Reg);
53
54
  //Ensure the transmit interrupted flag is set
55
  while(I2C_GetFlagStatus(I2C2,I2C_ISR_TCR) == RESET);
56
57
  I2C_TransferHandling(I2C2, PE_Addr, 1, I2C_AutoEnd_Mode, I2C_No_StartStop);
58
59
  //Again, wait until the transmit interrupted flag is set
60
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_TXIS) == RESET);
61
62
  //Send the value you wish you write to the register
63
  I2C_SendData(I2C2, Val);
64
65
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_STOPF) == RESET);
66
67
  //Clear the stop flag for the next potential transfer
68
  I2C_ClearFlag(I2C2, I2C_ISR_STOPF);
69
}


Nochmal danke, dass sich überhaupt jemand gemeldet hat bei so einem 
exotischen Controller.

von Robert V. (robert_v)


Lesenswert?

Hallo Zusammen!

Ich verwende das STM32F3 Discoveryboard und versuche den 
LidarLite-Sensor über I2C2 auch an denselben Pins zu initialisieren. Der 
Sensor verfügt über interne 4.7k PullUp-Widerstände. Ich nutze CooCox in 
der Version 1.7.8 zum Programmieren und Debuggen.

Das obige Programm habe ich praktisch 1zu1 übernommen, bei mir hängt es 
sich allerdings an genau derselben Stelle auf, an der es bei Killerawft 
zuerst aufhing, obwohl ich den überarbeiteten Code übernommen habe. Im 
main geht die Initialisierung durch, die Lidar_Write-Funktion hängt sich 
allerdings bei der ersten Flag-Abfrage auf.

Ich sitze schon den 2. Tag an dem Problem die Kommunikation zu 
initialisieren und komme nicht weiter.

Würdet ihr mir bitte dabei helfen Fehlerquellen auszuschließen? Ich gehe 
mittlerweile davon aus, dass es sich um ein Hardwareproblem handeln 
muss!?

Anbei mein Code:

DEFINES
1
#include "stm32f30x_gpio.h"
2
#include "stm32f30x_rcc.h"
3
#include "stm32f30x_i2c.h"
4
5
#include "stm32f30x.h"
6
#include <stddef.h>
7
#include <stdlib.h>
8
#include <stdbool.h>
9
10
/* Define LEDs */
11
12
#define    LIDARLite_ADDRESS    0x62       // Default I2C Address of LIDAR-Lite (7bit)
13
#define    LIDARLite_WRITE      0xC4      // Default Write Address of LIDAR-Lite.
14
#define    LIDARLite_READ      0xC5          // Default Read Address of LIDAR-Lite.

InitTypeDef
1
GPIO_InitTypeDef  GPIO_InitStructure;
2
I2C_InitTypeDef I2C_InitStructure;

I2C2_Initialiserung
1
void I2C2Init(void){
2
3
  // Initialisiert I2C2 an PA10 (SDA) und PA9 (SCL)
4
5
  //GPIO_InitTypeDef GPIO_I2C2;
6
  //I2C_InitTypeDef I2C_InitStructure;
7
8
  RCC_I2CCLKConfig(RCC_I2C2CLK_SYSCLK);
9
10
  // Enable GPIOA clock
11
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA,ENABLE);
12
  /* I2C2 clock enable */
13
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
14
15
  /* Alternate Function: config4 = I2Cx */
16
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_4);
17
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_4);
18
19
  /* Configure GPIO                */
20
21
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
22
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
23
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
24
  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
25
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
26
  GPIO_Init(GPIOA, &GPIO_InitStructure);
27
28
  /* Configure I2C2                */
29
30
  I2C_DeInit(I2C2);
31
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
32
  I2C_InitStructure.I2C_AnalogFilter = I2C_AnalogFilter_Enable;
33
  I2C_InitStructure.I2C_DigitalFilter = 0x00;
34
  I2C_InitStructure.I2C_OwnAddress1 = 0x00;
35
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
36
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
37
  I2C_InitStructure.I2C_Timing = 0x10805E89;
38
  // 100000 = 0x10805E89; Standard mode
39
  // 200000 = 0x00905E82; Fast Mode
40
  // 400000 = 0x00901850; // Fast Mode
41
  // 1000000= 0x00700818; // Fast Mode Plus
42
  I2C_Init(I2C2, &I2C_InitStructure); // I2C2 Init
43
  I2C_Cmd(I2C2,ENABLE); // enable I2C2
44
}

Funktion: I2C_Write_Lidar
1
void I2C_Write_Lidar(uint8_t RegAdd, uint8_t Data){
2
3
  /* Test on BUSY Flag */
4
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_BUSY) == SET);
5
6
  /* Configure slave address, nbytes, reload, end mode and start or stop generation */
7
  I2C_TransferHandling(I2C2, LIDARLite_ADDRESS, 1, I2C_Reload_Mode, I2C_Generate_Start_Write);
8
9
  /* Wait until TXIS flag is set */
10
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_TXIS) == RESET);    
11
12
13
14
        //!!HIER BLEIBT DAS PROGRAMM STEHEN
15
16
17
18
  /* Send Register address */
19
  I2C_SendData(I2C2, (uint8_t)RegAdd);            
20
21
  /* Wait until TCR flag is set */
22
  while(I2C_GetFlagStatus(I2C2,I2C_ISR_TCR) == RESET);    
23
24
  /* Configure slave address, nbytes, reload, end mode and start or stop generation */
25
26
  I2C_TransferHandling(I2C2, LIDARLite_ADDRESS, 1, I2C_AutoEnd_Mode, I2C_No_StartStop);
27
28
  /* Wait until TXIS flag is set */
29
30
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_TXIS) == RESET);    
31
32
  /* Write data to REGISTER */
33
34
  I2C_SendData(I2C2, (uint8_t)Data);            
35
36
  /* Wait until STOPF flag is set */
37
38
  while(I2C_GetFlagStatus(I2C2, I2C_ISR_STOPF) == RESET);
39
40
  /* Clear STOPF flag for the next potential transfer */
41
42
  I2C_ClearFlag(I2C2, I2C_ISR_STOPF);
43
}

von robert_v (Gast)


Lesenswert?

Und noch das main:
1
int main(void)
2
{
3
  SystemInit();
4
  I2C2Init();
5
6
  I2C_Write_Lidar(0x00,0x04);
7
8
  while (1);
9
  return 0;
10
}

von robert_v (Gast)


Lesenswert?

Am Ende war es ein Hardwareproblem.

Der Sensor hat seine Versorgungsspannung (5V) über ein externes Netzteil 
erhalten, das wohl zu einem leichten Einbruch auf der Clockleitung 
geführt hat, bevor der erste Clockimpuls kam. Die Grenzschwelle des 
Sensors scheint wohl sehr niedrig zu sein, da sie den Impuls erkannt 
haben muss. Der Sensor lieferte nur ein Nack.

Durch Verwendung der 5V-Versorgungsspannung auf dem Board wird nun das 
Ack geliefert.

von Che. (Gast)


Lesenswert?

Hi, ich habe ebenfalls das Problem, dass ich das Lidar Lite 2 über I2C 
im STM32 steuern möchte.

Ich nutze allerdings die CPAL von ST.

Das Initialisieren des I2C1-Devices im STM32 funktioniert.

Ich bekomme nach den Funktionen
i2cdevInit() und CPAL_I2C_IsDeviceReady() die Bestätigung.

Beim eigentlichen Lese- oder Schreibzugriff bekomme ich allerdings 
sofort ein NACK und alle weiteren Operationen sind obsolet.

Ich arbeite ebenfalls mit einer externen Spannungsversorgung.

Über Hilfe würde ich mich sehr freuen.

Viele Grüße,
Che

von rv (Gast)


Lesenswert?

Hi!

Verschicken die CPAL Funktionen ein Signal über I2C? Wenn ja, bekommst 
du da ein ACK? Falls sie kein Signal über I2C schicken, werden sie wohl 
den Bus initialisieren.

Es ist höchstwahrscheinlich ein Hardwareproblem. Schau dir mal die 
folgende Inetseite an: 
http://www.i2c-bus.org/i2c-primer/common-problems/

Oder passe Rp an:
http://www.i2c-bus.org/i2c-primer/typical-i2c-bus-setup/

Musst du ein externes Netzteil benutzen? Ändere Kabellängen.
Probier auf jeden Mal als Erstes die Spannungsversorgung vom Board um 
den Sensor in Betrieb zu nehmen. Als PullUps sorgten bei mir 2,2k für 
ein sauberes Signal.

Viel Glück, viel Erfolg!

von Che. (Gast)


Lesenswert?

Hi,

als allererstes vielen Dank für deine Antwort.

Die CPAL-Bibliothek ist ein abstraktes Interface um die I2C-Geräte zu 
initialisieren, drauf zu schreiben und zu lesen.

Leider habe ich kein I2C-Analysegerät (allerdings ein analoges 
Oszilloskop [VOLTCRAFT 632FG]), deswegen kann ich nicht bestätigen, dass 
mein STM32 tatsächlich ein I2C-Signal aussendet.
Da die Bibliothek so abstrakt ist, kann ich nicht nachvollziehen welche 
genauen Signale überhaupt versendet werden, und wie ich einzelne Bits 
schicke etc.

Ich arbeite auch nicht mit dem o.g. Discovery Board. Der STM32 ist Teil 
der Crazyflie 2.0 [Link: https://www.bitcraze.io/crazyflie-2/], deswegen 
muss ich die externe Spannungsquelle nutzen (Vcc liefert mir nur 3.0V). 
Evtl baue ich später einen Hochsetzsteller ein...

Die Seite, die du empfohlen hast, habe ich angeschaut.
*Die Spannungsversorgung aus dem Netzteil sollte stabil sein.
*Kabellängen habe ich gerade varriert, leider ohne Erfolg.
*Die Pullups habe ich auf ca. 3k gesetzt, allerdings glaube ich, dass 
das System so konfiguriert ist, dass der SDA intern schon einen Pullup 
hat (laut Entwicklern 
(https://forum.bitcraze.io/viewtopic.php?f=6&t=1733&start=20)).

*Wie checke ich denn, ob die I2C-Supply-Voltages kompatibel sind? Ich 
dachte, die wären genormt.


Hast du vielleicht noch einen Tipp? Ich könnte mir auch vorstellen, auf 
die CPAL zu verzichten. Allerdings kann ich mir gerade noch nicht 
vorstellen, wie ich die Kommunikation dann ins Gesamtsystem einbinde 
(FreeRTOS).

Viele Grüße,
Che

von rv (Gast)


Lesenswert?

Was mir gerade einfällt:

Ich musste die Addressierung des LidarLite um ein Bit nach links 
shiften.
1
// Default I2C Address of LIDAR-Lite (7bit)
2
#define    LIDARLITE_PHYSICAL_ADDRESS    0x62
3
#define    LIDARLITE_ADDRESS        (LIDARLITE_PHYSICAL_ADDRESS << 1)

Benutz mal LIDARLITE_ADDRESS, die Beschreibung des Herstellers mit den 
Read und Write Addressen ist irreführend, da das neunte Bit vom 
Addressat geliefert wird(N/ACK). Vielleicht löst das bereits dein 
Problem.

Ich benutze noch die StdPeriphLib. Die CPAL wurde erst vor Kurzem 
eingeführt. Ich habe mich auch kurz damit befasst, bin aber dann doch 
lieber konservativ geblieben. Welche Software benutzt dein Crazyfly? 
Wenn andere Sensorik bereits implementiert ist, kannst du das im 
entsprechenden .c-File nachschauen ob die StdPeriphLib bereits benutzt 
wird. Das ist wohl eher wahrscheinlich, da deine Software wohl schon 
seid geraumer Zeit verwendet wird. Ein RTOS hin oder her.
Bei einem RTOS werden Tasks initialisiert in denen deine Funktionen 
ausgeführt werden. Die stm32f3-spezifischen Funktionen, wie die 
i2c-initialisierung, wird vorher definiert. Wenn deine Software nicht 
auf CPAL beruht, muss die CPAL mit eingebunden werden. Das frisst 
sicherlich einige Ressourcen. Bedenke, das bei einem Softwareprojekt wie 
bei deinem der Flash schnell gefüllt sein kann.

Viele Grüße

von Che. (Gast)


Lesenswert?

Den Tipp, die Adresse zu shiften, habe ich direkt ausprobiert. Ich 
bekomme immernoch den Errorcallback 0x0400

https://github.com/bitcraze/crazyflie-firmware/blob/master/lib/STM32_CPAL_Driver/inc/cpal_i2c.h#L104

Bitcraze hat ein Interface geschrieben, dass nochmal über der 
CPAL-Bibliothek liegt. Diese Zugriffsfunktionen shiften die Adresse 
ebenfalls, deswegen bin ich unsicher, ob das Vorherige shiften notwendig 
ist. Was meinst du dazu?
Beispiel:
1
 bool i2cdevRead(I2C_Dev *dev, uint8_t devAddress, uint8_t memAddress,
2
               uint16_t len, uint8_t *data)
3
{
4
  dev->pCPAL_TransferRx->wNumData = len;
5
  dev->pCPAL_TransferRx->pbBuffer = data;
6
  dev->pCPAL_TransferRx->wAddr1 = devAddress << 1;
7
  dev->pCPAL_TransferRx->wAddr2 = memAddress;
8
9
  dev->wCPAL_Options = CPAL_OPT_I2C_NOSTOP_MODE;
10
  if (memAddress == I2CDEV_NO_MEM_ADDR)
11
  {
12
    dev->wCPAL_Options |= CPAL_OPT_NO_MEM_ADDR;
13
  }
14
 
15
  return i2cdevReadTransfer(dev);
16
}

Auf der Crazyflie ist noch ein anderes I2C-Device (IMU) verbaut, das 
wird ebenfalls  per CPAL implementiert. Das EEPROM im übrigen auch.

So wie ich das sehe, ist die StdPeriphLib auch verfügbar, ob die 
irgendwo genutzt wird, habe ich noch nicht gecheckt.

Es handelt sich auch um den STM32F405. Aber die Bibliotheken sind alle 
vorhanden, glaube nicht, dass das eine Rolle spielt (nur FYI).

Ich würde jetzt als nächsten Ansatz das Lidar per StdPeriphLib versuchen
einzubinden. Oder mache ich da jetzt einen Denkfehler?

Viele Grüße,
Che.

von rv (Gast)


Lesenswert?

Ja, das shiften wird in der Funktion bereits ausgeführt.

Der eignetliche Transfer findet in der Funktion i2cdevReadTransfer 
statt. Überprüfe die Funktion darauf ob sie tatsächlich nach der 
Registeraddressierung eine STOP und eine START-Kondition ausführt:
http://lidarlite.com/docs/v2/i2c_protocol_summary/
Es könnte nämlich sein, dass das NACK dann ankommt, wenn das anders 
ausgeführt wird. Diese Vorgehensweise ist nicht immer so.

Testest du immer die Read-Funktion? Nimm die WriteFunktion um weitere 
Fehlerquellen auszuschliessen. Du solltest auch erstmal den Sensor 
konfigurieren, mit 0x00 an 0x00(Default).
(siehe configure-Funktion: 
https://github.com/PulsedLight3D/LIDARLite_v2_Arduino_Library/blob/master/LIDARLite/LIDARLite.cpp)

Auf der Crazyfly könnte der andere I2C-Bus für die erwähnten Devices 
benutzt werden. Stelle sicher, dass der I2C-Bus, den du benutzt 
initialisiert wird.

Wie gesagt, es ist aller Wahrscheinlichkeit nach ein Hardwareproblem, 
daher eine andere Library zu benutzen unnötig, es wird wieder auf das 
NACK hinauslaufen. Nimm das Oszilloskop und schau das Signal an. Achte 
auch darauf, dass bei der Write-Funktion der Bus seine 100kHZ hat. Wenn 
die Vorraussetzungen stimmen, dann liegt es an der ext. 
Spannungsversorgung.

Hast du ein anderes Dev.Kit zur Verfügung? Ein Discoveryboard vllt? 
Teste auch mal den PWM-Output um die Funktion des Lidarsensors zu 
verifizieren. Hast du eine andere Spannungsquelle? Akku, oder ein Board 
mit Spannungswandlern?

von Che. (Gast)


Lesenswert?

Hi,
die Funktion sieht folgendermaßen aus:
1
 static bool i2cdevReadTransfer(I2C_Dev *dev)
2
{
3
  bool status;
4
5
  dev->CPAL_Mode = CPAL_MODE_MASTER;
6
  /* Force the CPAL state to ready (in case a read operation has been initiated) */
7
  dev->CPAL_State = CPAL_STATE_READY;
8
  /* Start writing data in master mode */
9
  status = CPAL_I2C_Read(dev);
10
11
  if (status == CPAL_PASS)
12
  {
13
    i2cDevTakeSemaphore(dev->CPAL_Dev);
14
15
    //TODO: Remove spin loop below. It does not work without it at the moment
16
    while(dev->CPAL_State != CPAL_STATE_READY && dev->CPAL_State != CPAL_STATE_ERROR);
17
    status = (dev->CPAL_State == CPAL_STATE_READY) || (dev->CPAL_State != CPAL_STATE_ERROR);
18
  }
19
20
  return status;
21
}

Wie man sieht, geht das Durchreichen von "dev" hier weiter. Konditionen 
werden erst in der CPAL gesetzt.
Die Funktion CPAL_I2C_Read() hat ca 250 Zeilen mit vielen If-Defs.

Ich versuche mal den meiner Meinung nach wesentlichen Teil zu 
extrahieren:
1
...
2
...
3
#ifdef CPAL_I2C_MASTER_MODE    
4
    /* If "No Memory Address" Option Bit is not selected and Master Mode selected */
5
    if (((pDevInitStruct->wCPAL_Options & CPAL_OPT_NO_MEM_ADDR) == 0) 
6
       && (pDevInitStruct->CPAL_Mode == CPAL_MODE_MASTER ))
7
    {       
8
      CPAL_LOG("\n\rLOG : I2C Device Master No Addr Mem Mode");
9
      
10
      /* Generate Start */
11
      __CPAL_I2C_HAL_START(pDevInitStruct->CPAL_Dev);
12
      
13
      /* Wait until SB flag is set */
14
      __CPAL_I2C_TIMEOUT(__CPAL_I2C_HAL_GET_SB(pDevInitStruct->CPAL_Dev), CPAL_I2C_TIMEOUT_SB);
15
      
16
      /* Send Device Address */
17
      /* If 7 Bit Addressing Mode */
18
      if (pDevInitStruct->pCPAL_I2C_Struct->I2C_AcknowledgedAddress == I2C_AcknowledgedAddress_7bit)
19
      {             
20
        /* Send Slave address with bit0 reset for write */
21
        __CPAL_I2C_HAL_SEND((pDevInitStruct->CPAL_Dev), (uint8_t)((pDevInitStruct->pCPAL_TransferRx->wAddr1) & (uint8_t)(~I2C_OAR1_ADD0)));   
22
        
23
        /* Wait until ADDR flag is reset */ 
24
        __CPAL_I2C_TIMEOUT(__CPAL_I2C_HAL_GET_ADDR(pDevInitStruct->CPAL_Dev), CPAL_I2C_TIMEOUT_ADDR);        
25
      }
26
...
27
...

Reicht es also aus, vor dem
      __CPAL_I2C_HAL_START(pDevInitStruct->CPAL_Dev);
ein
      __CPAL_I2C_HAL_STOP(pDevInitStruct->CPAL_Dev);
zu setzen?

Ich teste das, bevor ich deine anderen Tipps durcharbeite.

von Che. (Gast)


Lesenswert?

PS.

Die i2cdevReadTransfer habe ich bereits umgeändert, ich habe die 
NOSTOP-Option auskommentiert.

Seit dem ändert sich das Verhalten folgendermaßen.

*Kein NACK mehr bei schreiben von 0x04 auf 0x00 (Lesemodus des Lidar 
aktivieren lt. Handbuch).

*Kein Nack mehr bei lesen von diversen Registern, allerdings sind alle 
Werte aus allen Registern und Adressen dann null.

*Nach ein paar Zyklen bekomme ich schließlich manchmal doch noch ein 
NACK-Fehler, woraufhin alle Register den Wert 32 bekommen [0x20 Hex = 
0010 0000]
BIS AUF das Statusregister 0x01. Dort habe ich den Dezimalen Wert 224 
[0xE0 = 1110 0000]. Lt. Handbuch wäre das sogar ein halbwegs plausibler 
Wert.

von rv (Gast)


Lesenswert?

Na das sieht doch schon mal gut aus!

Was die Statusbits bedeuten, kann nachgeschlagen werden:

(0x01 - Mode/Status (control_reg[1]:): 
http://lidarlite.com/docs/v2/registers/)

Ich würde jetzt eine einzelne Distanzmessung vornehmen:
siehe distance - Funktion
(https://github.com/PulsedLight3D/LIDARLite_v2_Arduino_Library/blob/master/LIDARLite/LIDARLite.cpp)

Passen die Distanzwerte in etwa? Die Distance-Write-Anweisung führt zu 
einer Akquirierungsanweisung. Je nachdem wie die Licht und 
Signalstärkeverhältnisse sind kann eine Akquirierung bis zu 20ms 
andauern. Hierzu werden intern mehrere Messungen vorgenommen. Die 
Messung ist vollendet sobald eines der Status-Overflow-Flags gesetzt 
wird(zu schlechte Verhältnisse führen zu einer fehlerhaften Messung(bsp. 
>4000cm)ohne setzen eines der Bytes). Entweder Flag überprüfen und dann 
mit der ReadFunktion 2 Bytes von 0x8f auslesen, oder eine Mindestzeit 
abwarten. Das Auslesen des Registers während der nicht abgeschlossenen 
Akquirierung führt zu möglicherweise fehlerhaften Werten.

Viel Erfolg!

von rv (Gast)


Lesenswert?

rv schrieb:
> (bsp.
>>4000cm)ohne setzen eines der *Bytes)

*Flagbits

von Che. (Gast)


Lesenswert?

So, also die CPAL-Funktion kann ich natürlich nicht einfach ändern, da 
die Bibliothek ja noch von dem anderen o.g. Gerät verwendet wird. Wenn 
ich da einfach ein STOP einfüge funktioniert das natürlich nicht mehr.

Ich habe das Lidar auf meinem Raspberry mit derselben Konfiguration 
erfolgreich getestet. Externe Spannungsquelle, Raspi und Lidar nur per 
SDA, Clock und GND verbunden.

Wie teste ich denn mit dem Analogen Oszi auf die richtige Frequent beim 
Schreiben? Bräuchte da vielleicht noch einen Hinweis.

LG,
Che.

von Che. (Gast)


Lesenswert?

Da haben unsere Antworten sich überschnitten.

Also ich habe auch die Higher und Lower Abstandsregister ausgelesen 
(habe aber bei beiden den Wert 0, wo er eigentlich etwas drin stehen 
haben müsste).

Ich bin mir noch nicht sicher, ob die Software-Konfiguration schon so 
stimmt.

von rv (Gast)


Lesenswert?

Beim Oszi Wellenlänge ausmessen und Frequenz bestimmen: f=1/lambda

Wenn die Register 0 wiedergeben, was sagt das Bit3 des Stausbytes?

Sicher, dass du die Write und Read-Anweisungen korrekt ausführst? Falls 
es immer noch nicht klappt, füg mal den entsprechenden Code an. Dann 
kann man sich das anschauen.

von Che. (Gast)


Angehängte Dateien:

Lesenswert?

Im Anhang meine .c Datei und ein Screenshot von der Konsolenausgabe 
(konnte ich leider nicht anders einbinden, da meine Virtuelle Maschine 
kein C&P zulässt).

Ein paar 0-Zyklen habe ich aus der Ausgabe gelöscht um auch den Fehler 
darzustellen.

von Che. (Gast)


Lesenswert?

Hier noch meine Funktion
1
 
2
uint8_t lidar_getRegister(uint16_t registerAddr)
3
{
4
  uint8_t data;  //Read a data byte from an internal register of the LIDAR
5
  i2cdevRead(I2Cx, devAddr, registerAddr, 1, &data);
6
7
  return data;
8
}

von rv (Gast)


Lesenswert?

In void rangeInit(void) liefert lidarInit(I2C1_DEV); oder lidarTest(); 
I2C connection FAIL!?

Du solltest die Konfiguration in deiner Initialisierung vornehmen, da du 
sonst in der While-Schleife immer wieder neu konfigurierst.

In der While-Schleife einfach mal nur:
Akquierierungsanweisung, und auslesen.

Ist uint16_t registerAddr tatsächlich 16bit lang?

In der Funktion lidar_getRegister() übergibst du nur registerAddr, aber 
nicht I2Cx und devAddr. Das musst du anpassen, so kann das gar nicht 
funktionieren. Sieht lidar_setRegister() auch so aus? Die Parameter 
müssen korrekt an i2cdevRead/Write weitergegeben werden.

Benutze lieber vorerst die auskommentierten Funktionen
1
//PERFORM SIGNAL ACQUISITION
2
    DEBUG_PRINT("PERFORM SIGNAL ACQUISITION...\n");
3
    i2cdevWriteByte(I2C1_DEV, LIDARLITE_ADDRESS, 0x00, 0x04);
4
5
    //READ STATUS REGISTER
6
    DEBUG_PRINT("READ STATUS REGISTER...\n");
7
    i2cdevReadByte(I2C1_DEV, LIDARLITE_ADDRESS, LIDARLITE_REG_STATUS2, pData);
Bei der ReadFunktion den 5. Parameter nicht vergessen: Du kannst auch 
gleich 2 bytes aus 0x8f lesen.
Und bitte nicht immer den FPGA Resetten:
Einmal konfigurieren und dann in der Schleife erstmal 
Akquirierungsanweisung und auslesen.

von Che. (Gast)


Lesenswert?

lidarTest() liefert I2C connection FAIL. Ich habe die Funktion aus einem 
anderen I2C-Device kopiert (von Bitcraze), ganz verstanden habe ich es 
nicht, daher hab ichs gerade wieder rausgeworfen.

In dem Code, den ich angehängt hatte, sind die Resets vom FPGA 
auskommentiert. Leider ist das Kommentierungszeichen nach links 
verrutscht, sorry dafür. Oder meintest du noch eine andere Stelle?

Ich habe jetzt den Reset in der Init-Funktion stehen.

Im Loop lediglich die Prints, den Acquisition (write) und den Read.

Ich bekomme da jedoch von dem Register im Data dann immernoch die 0.

Die uint16 habe ich auf uint8 geändert.

rv schrieb:
> In der Funktion lidar_getRegister() übergibst du nur registerAddr, aber
> nicht I2Cx und devAddr. Das musst du anpassen, so kann das gar nicht
> funktionieren. Sieht lidar_setRegister() auch so aus? Die Parameter
> müssen korrekt an i2cdevRead/Write weitergegeben werden.

Ich habe hier die lidar.c nicht gepostet. Da habe ich einen Platzhalter 
für I2C1 definiert, damit man auch die anderen beiden Devices benutzen 
kann. Das Prinzip habe ich ebenfalls von Bitcraze übernommen.
1
#define DEBUG_MODULE "LIDAR"
2
3
#include "FreeRTOS.h"
4
#include "task.h"
5
6
#include "lidarlite.h"
7
#include "debug.h"
8
#include "eprintf.h"
9
10
#include "cpal.h"
11
12
13
static uint8_t devAddr;
14
static I2C_Dev *I2Cx;
15
static bool isInit;
16
17
18
bool lidarInit(I2C_Dev *i2cPort)
19
{
20
  if (isInit)
21
    return true;
22
23
  I2Cx = i2cPort;
24
  devAddr = LIDARLITE_ADDRESS;
25
26
27
  isInit = true;
28
29
  return true;
30
31
}

von rv (Gast)


Lesenswert?

Bevor der Code näher untersucht wird:
Hast du das I2C-Signal bereits mit dem Oszilloskop verifiziert?
Bitte als Erstes nachholen um zu überprüfen, ob der Code wirklich das 
tut was er soll und was du denkst, das er macht.
Vielleicht hast du ja auch Zugriff auf einen Analyzer wie bspw. 
IKALOGIC, ist aber nicht absolut notwendig.

von Che. (Gast)


Lesenswert?

Ich weiß nicht genau, was ich mit dem Oszilloskop messen soll. Das es 
sich um ein Analogmessgerät handelt, hatte ich ja schon erwähnt.

Ich dachte, damit lassen sich nur periodische Signale darstellen.
Also könnte ich höchstens das Clock-Signal verifizieren, aber wie genau? 
Ich bekomme immer nur kleine Bursts angezeigt, ungefähr im Abstand der 
Zykluszeit meiner Task. Aber ob die Frequenz stimmt, kann ich nicht 
sagen.

Viele Grüße

von rv (Gast)


Lesenswert?

Du kannst beide Signale triggern. Dann die Auflösung verändern um die 
"Bursts" genauer anzuschauen. Lerne den Umgang mit dem Oszi, es ist 
neben dem Multimeter das wichtigste Messinstrument eines Elektronikers!

von Che. (Gast)



Lesenswert?

Hi, ich musste etwas rumfrickeln, damit das Signal stabil bleibt.
So siehts jedenfalls aus, unten ist das Clock-Signal und oben das 
Data-Signal zu sehen.
Mir scheint es, als wäre die Ausgabe des STM32 in Ordnung, allerdings 
bekomme ich vom Lidar keinerlei Rückmeldung.

Viele Grüße

von rv (Gast)


Lesenswert?

Ich habe gerade keine Kamera da um dir zu zeigen, wie das aussehen 
sollte.
Ich kann es nicht genau erkennen, aber verändert sich die Frequenz auf 
der Clockleitung?
Hast du noch ein zweites Signal auf der Leitung? -> DC-Signal bei SDA ab 
ca. der Hälfte.
Das sieht insgesamt nicht gesund aus.
Zunächst mal die Frage: Sind noch andere Devices an dem Bus 
angeschlossen, oder besteht die Kommunikation nur mit dem LidarSensor?
Falls noch andere Devices am Bus liegen, abschalten. Nur die Komm. mit 
dem LidarSensor verifizieren.

Allgemein würde ich empfehlen den LidarSensor unabhängig von der 
CrazyflySoftware zu testen, da es ja auf Anhieb nicht funktioniert. So 
werden einige Fehlerquellen ausgeschlossen.
-> Eigenes Projekt nur für den Lidar aufsetzen.

von Che. (Gast)


Lesenswert?

Man könnte es annehmen, dem Bild zufolge, dass sich die Clock verändert. 
Aber kann das nicht auch an der Triggerung liegen?

Die Leitung sollte exklusiv für das Lidar genutzt werden. Ich habe kein 
zweites Device dranhängen.

Ich würde als nächsten Schritt meine betreuende Professorin fragen, ob 
ich ein STM32 Dev Kit gesponsort bekomme, das Lidar erstmal nur mit dem 
STM32 zu testen scheint mir auch der sinnvollste Weg.

Falls du eine Kamera organisieren kannst (evtl vom Smartphone?) würde 
ich mich trotzdem noch über ein Foto freuen.


Viele Grüße

von Che. (Gast)


Lesenswert?

Es sieht so aus, als würde die CPAL keine STOP-Condition nach dem lesen 
senden. Deswegen bekommt man keine Werte zurück, da das Lidar nach bzw 
vor jedem Vorgang ein STOP braucht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.