Forum: Mikrocontroller und Digitale Elektronik STM32 und I2C - Fehler im Code


von Viktor B. (coldlogic)


Lesenswert?

Hi Leute,

ich versuche gerade ein OLED-Display per I2C anzusteuern. Das Display 
funktionierte in einer anderen Schaltung, ist also in Ordnung. Pull-ups 
sind angeschlossen. Es klappt leider nicht mit dem Code - vermute ich, 
weil beim debuggen 80% aller Programmstopps im Interrupt landen. Könnte 
jemand mal meinen Code ansehen? Von alleine finde ich den Fehler nicht. 
Andere Beispiele wie z.B. der von Driller und offizielle ST-Doku habe 
ich mir schon durchgeschaut, will die aber nicht guttenbergen. Hier ist 
das Programm:
1
#include "stm32f10x_conf.h"
2
//#include "stm32f10x.h"
3
4
void Init(void){
5
// Hardware initialiser
6
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);                // enable I2C;
7
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);                // enable alternate functions (why?);
8
9
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);               // GPIO C enable
10
    GPIO_InitTypeDef gpioInit;
11
    gpioInit.GPIO_Mode = GPIO_Mode_Out_OD;
12
    gpioInit.GPIO_Speed = GPIO_Speed_2MHz;
13
    gpioInit.GPIO_Pin = GPIO_Pin_13;
14
    GPIO_Init(GPIOC, &gpioInit);
15
    GPIO_WriteBit(GPIOC, ((uint16_t)0x2000), Bit_SET);
16
17
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);               // GPIO B enable
18
    GPIO_InitTypeDef gpioInit1;
19
    gpioInit1.GPIO_Mode = GPIO_Mode_AF_OD;
20
    gpioInit1.GPIO_Speed = GPIO_Speed_50MHz;
21
    gpioInit1.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;                       // route I2C pins
22
    GPIO_Init(GPIOB, &gpioInit1);
23
24
    NVIC_InitTypeDef NVICinit;                                          // configure NVIC
25
    NVICinit.NVIC_IRQChannel = I2C1_EV_IRQn;                            // add I2C_event interrupt
26
    NVICinit.NVIC_IRQChannelPreemptionPriority = 0;
27
    NVICinit.NVIC_IRQChannelSubPriority = 0;
28
    NVICinit.NVIC_IRQChannelCmd = ENABLE;
29
    NVIC_Init(&NVICinit);
30
31
    NVICinit.NVIC_IRQChannel = I2C1_ER_IRQn;                            // add I2C_error interrupt
32
    NVICinit.NVIC_IRQChannelPreemptionPriority = 0;
33
    NVICinit.NVIC_IRQChannelSubPriority = 0;
34
    NVICinit.NVIC_IRQChannelCmd = ENABLE;
35
    NVIC_Init(&NVICinit);
36
37
    I2C_DeInit(I2C1);                                                   // init I2C module
38
    I2C_InitTypeDef I2Cinit;
39
    I2Cinit.I2C_Ack = I2C_Ack_Enable;
40
    I2Cinit.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
41
    I2Cinit.I2C_ClockSpeed = 100000;
42
    I2Cinit.I2C_DutyCycle = I2C_DutyCycle_2;
43
    I2Cinit.I2C_Mode = I2C_Mode_I2C;
44
    I2Cinit.I2C_OwnAddress1 = 0;
45
    I2C_Init(I2C1, &I2Cinit);
46
47
    I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);                             // enable event, error and buffer interrupts
48
    I2C_ITConfig(I2C1, I2C_IT_ERR, ENABLE);
49
    I2C_ITConfig(I2C1, I2C_IT_BUF, ENABLE);
50
51
    I2C_Cmd(I2C1, ENABLE);
52
}
53
54
volatile uint8_t I2C_errorcode = 0, I2C_addr = 0, I2C_num_bytes = 0;
55
volatile uint8_t* I2C_reading_pointer;
56
static uint8_t OLED_init[29]= {                                          // Initialization Sequence
57
0x00,                                                                   // Commands will be sent
58
0xAE,                                                              // Display OFF (sleep mode)
59
0x20, 0b00,                                                            // Set Memory Addressing Mode
60
                                                                        // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
61
                                                                        // 10=Page Addressing Mode (RESET); 11=Invalid
62
0xB0,                                                              // Set Page Start Address for Page Addressing Mode, 0-7
63
0xC8,                                                              // Set COM Output Scan Direction
64
0x00,                                                                // --set low column address
65
0x10,                                                              // --set high column address
66
0x40,                                                              // --set start line address
67
0x81, 0x3F,                                                            // Set contrast control register
68
0xA1,                                                              // Set Segment Re-map. A0=address mapped; A1=address 127 mapped.
69
0xA6,                                                              // Set display mode. A6=Normal; A7=Inverse
70
0xA8, 63,                                                            // Set multiplex ratio(1 to 64)
71
0xA4,                                                              // Output RAM to Display
72
                                                                        // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
73
0xD3, 0x00,                                                            // Set display offset. 00 = no offset
74
0xD5,                                                              // --set display clock divide ratio/oscillator frequency
75
0xF0,                                                              // --set divide ratio
76
0xD9, 0x22,                                                            // Set pre-charge period
77
0xDA, 0x12,                                                            // Set com pins hardware configuration
78
0xDB,                                                              // --set vcomh
79
0x20,                                                              // 0x20,0.77xVcc
80
0x8D, 0x14,                                                            // Set DC-DC enable
81
0xAF                                                                    //?
82
};
83
84
    void I2C1_EV_IRQHandler(){                                              // Self-containing I2C interrupt
85
        if(I2C1->SR1 & I2C_SR1_SB){                                         // triggered by start condition ->
86
            I2C1->DR = I2C_addr;                                            // send I2C adress
87
        }else if((I2C1->SR1 & I2C_SR1_ADDR)||(I2C1->SR1 & I2C_SR1_TXE)){    // if not, then send data
88
           if(I2C_num_bytes){                                               // in case its available
89
                I2C1->DR = *I2C_reading_pointer;
90
                I2C_reading_pointer++;
91
                I2C_num_bytes--;
92
            }
93
        }                                                                   // if not - send stop condition
94
        else if((I2C1->SR1 & I2C_SR1_BTF)){
95
            I2C1->CR1 |= ((uint16_t)0x0200);                                // EmBitz does not recognize CR1_STOP_Set
96
            I2C_errorcode = 0;
97
        }
98
    }
99
100
    void I2C1_ER_IRQHandler(){
101
        if(I2C1->SR1 & I2C_SR1_AF)
102
            I2C_errorcode = 1;                                              // No devise with that adress
103
        else{
104
            I2C_errorcode = 2;                                              // Something strange occured, inspect SR1
105
        }
106
    }
107
108
    void I2C_initiate_transmission(){                                       // it`s fire and forget
109
        while(I2C1->SR2 & I2C_SR2_BUSY){}                                   // wait for the bus to become free (maybe not required)
110
        I2C1->CR1 |= ((uint16_t)0x0100);                                    // send start condition; EmBitz doesnt recognize it also
111
        I2C_errorcode = -1;                                                 // lock the I2C variables
112
    }
113
114
115
int main(void)
116
{
117
    Init();
118
119
120
    I2C_reading_pointer = OLED_init;
121
    I2C_num_bytes = 29;
122
    I2C_addr = 0x78;
123
124
    I2C_initiate_transmission();
125
126
    while(I2C_errorcode != 0){}
127
128
    uint8_t foo[2] = {0x40,0xff};
129
130
    I2C_reading_pointer = foo;
131
    I2C_num_bytes = 2;
132
133
    I2C_initiate_transmission();
134
135
  while(1)
136
  {
137
138
  }
139
}

von Rätsel Rater (Gast)


Lesenswert?

Viktor B. schrieb:
> I2C_ITConfig(I2C1, I2C_IT_BUF, ENABLE);

... und wo ist dein Interrupt-Handler dafür?

Viktor B. schrieb:
> // enable alternate functions (why?);

Wenn du nicht weisst warum, warum machst du es dann?

Viktor B. schrieb:
> // EmBitz does not recognize CR1_STOP_Set
> // send start condition; EmBitz doesnt recognize it also

Was hat EmBitz damit zu tun? Gar nichts. Der Programmierer
ist dafür verantwortlich dass er dem Compiler die Symbol-
definitionen bekannt gibt.

Viktor B. schrieb:
> // No devise with that adress

<device> schreiben ist die Devise

von Rätsel Rater (Gast)


Lesenswert?

Viktor B. schrieb:
> // EmBitz does not recognize CR1_STOP_Set

Merke: EmBitz ist kein Compiler.

von Stefan F. (Gast)


Lesenswert?

Damit wir hier ein bisschen konkreter auf die Bedürfnisse des Displays 
eingehen können, solltest du es benennen (bzw. dessen Controller).

An Deiner Stelle würde ich die Kommunikation mit einem Logik-Analyzer 
untersuchen. Stimmt sie mit dem Quelltext überein? Wenn nicht, hast du 
wohl die I²C Schnittstelle falsch benutzt. Wenn sie jedoch überein 
stimmt, ist etwas an der Initialisierungs-Sequenz falsch.

Falls es wie ich vermute ein SSD1306 sein sollte, darfst du gerne von 
meinem Code abgucken. 
http://stefanfrings.de/esp8266/WIFI-Kit-8-Test2.zip

Meine Initialisierungssequenz ist etwas anders als deine. Sie beruht auf 
Informationen aus zwei unterschiedlichen Datenblättern, plus diversen 
Tipps die ich im Internet gefunden habe. Ich sehe da einige Unterschiede 
(abgesehen von vermutlich harmlosen Abweichungen bei der Reihenfolge der 
Kommandos).

Falls es kein SSD1306 ist, könnte das Display eventuell Pausen zwischen 
bestimmten Kommandos benötigen.

von Stefan F. (Gast)


Lesenswert?

Noch was: Eventuell magst du zuerst mal die Bit-Banging Methode aus 
meinem Code mit deiner Initialisierungssequenz versuchen. Oder du 
versuchst es mit der Polling Methode wie hier umgesetzt: 
http://stefanfrings.de/stm32/index.html#i2c Beide kann man tadellos 
debuggen.

So kannst du wenigstens herausfinden, ob der Fehler in deiner 
Initialisierungssequenz steckt, oder woanders.

von Viktor B. (coldlogic)


Lesenswert?

Rätsel Rater schrieb:
> Viktor B. schrieb:
>> I2C_ITConfig(I2C1, I2C_IT_BUF, ENABLE);
>
> ... und wo ist dein Interrupt-Handler dafür?

Es gibt nur 2 Interruptvektoren - Event und Error. Quelle: Reference 
Manual für STM32F10x Controller, Seite 772, Abbildung 278, I2C interrupt 
mapping diagram.

Rätsel Rater schrieb:
> Viktor B. schrieb:
>> // enable alternate functions (why?);
>
> Wenn du nicht weisst warum, warum machst du es dann?

Weil es in einem der Beispiele so stand. War mir nicht sicher, was es 
sollte, deswegen hab ich es im ursprünglichem Zustand gelassen.

Rätsel Rater schrieb:
> Viktor B. schrieb:
>> // EmBitz does not recognize CR1_STOP_Set
>> // send start condition; EmBitz doesnt recognize it also
>
> Was hat EmBitz damit zu tun? Gar nichts. Der Programmierer
> ist dafür verantwortlich dass er dem Compiler die Symbol-
> definitionen bekannt gibt.

Rätsel Rater schrieb:
> Viktor B. schrieb:
>> // EmBitz does not recognize CR1_STOP_Set
>
> Merke: EmBitz ist kein Compiler.

Ich habe auch nicht gesagt, EmBitz wäre ein Compiler. Und ja, der 
Compiler hinter EmBitz erkennt zwar andere Definitionen aus der selben 
Datei, aber genau die eine irgendwie nicht. Was soll man da bloß machen?

Rätsel Rater schrieb:
> Viktor B. schrieb:
>> // No devise with that adress
>
> <device> schreiben ist die Devise

Oh bitte, war das nötig?

Stefanus F. schrieb:
> An Deiner Stelle würde ich die Kommunikation mit einem Logik-Analyzer
> untersuchen.

EInen Logik Analyzer habe ich mit vor einigen Tagen bestellt, der ist 
leider noch nicht angekommen. Den I2C-Ausgang werde ich so schnell wie 
möglich damit testen.

Das Display ist - richig geschätzt - ein SSD1306. Die 
Initialisierungssequenz ist aus einem anderen Projekt, wo es zuverlässig 
funktioniert, d.h. an der Sequenz wird es erstmal nicht liegen. Ich bin 
mir sicher, ich hätte den I2C-Code vermurkst, aber wie?.. Interrupt 
flags sollen sich durch Hardware beim Lesen der Status-Register 
rücksetzen. Den I2C-takt habe ich eingeschaltet. Welche weit 
verbreiteten Fehler gibt es noch?

von Rätsel Rater (Gast)


Lesenswert?

Viktor B. schrieb:
> Ich habe auch nicht gesagt, EmBitz wäre ein Compiler.

Sehr wohl hast du das (indirekt) gesagt, denn du hast EmBitz
die Schuld zugeschrieben dass er es nicht findet. Dabei ist
es eben der Compiler der es nicht findet. Und genau deswegen
weil du ihm nicht gesagt hast in welcher Datei er es finden
soll. Dumm wäre es jetzt stm32f10x_i2c.c zu inkludieren.

Der Grund ist vermutlich eine gewisse Absicht von den SPL-Machern,
da sie die entsprechenden Defines "privat" gemacht haben.
Du solltest I2C_GenerateSTOP (..) aus stm32f10x_i2c.c verwenden.
Gleiches gilt für I2C_GenerateSTART (..)


Viktor B. schrieb:
> Es gibt nur 2 Interruptvektoren - Event und Error.

Ok, das war mein Wissensdefizit.

Dann solltest du in den Interrupt-Handlern noch entsprechend
gesetzte Flags mit I2C_ClearITPendingBit (..) löschen.

von Viktor B. (coldlogic)


Lesenswert?

Rätsel Rater schrieb:
> Dumm wäre es jetzt stm32f10x_i2c.c zu inkludieren.

Sollte schon mit conf.h drin sein.

Rätsel Rater schrieb:
> Du solltest I2C_GenerateSTOP (..) aus stm32f10x_i2c.c verwenden.
> Gleiches gilt für I2C_GenerateSTART (..)

Die Methode I2C_GenerateStart und die andere sind dasselbe wie I2C1->CR1 
|= I2C_CR1_START, nur halt mit einem assert, damit ich bloß nicht sowas 
wie TIM1->OCRA |= I2C_CR1_START compiliere (klappen würde es ja, wäre 
trotzdem eine schlechte Idee.) Für einen kleineren Projekt fügt das nur 
Abstraktionslayer hinzu.

Rätsel Rater schrieb:
> ann solltest du in den Interrupt-Handlern noch entsprechend
> gesetzte Flags mit I2C_ClearITPendingBit (..) löschen.

Diese werden von der Hardware gelöscht, wenn man die Register SR1 und 
SR2 ausliest. Außerdem sind die Bits als "read-only" im Reference Manual 
markiert.

von Viktor B. (coldlogic)


Lesenswert?

Viel Debugging später hat sich gezeigt, dass der Programmfluss im 
Interrupt gestört wird bzw. dort der Fehler liegt. Irgendwie springt es 
von der Zeile
1
          if(I2C_num_bytes){                     // in case its available

bei I2C_num_bytes = 0 anstatt zur nächsten schließenden geschweiften 
Klammmer einfach völlig aus dem Interrupt. Ich bin ehrlich gesagt 
ratlos.

UPD.: Außerdem enthält das Register I2C1->SR1 auch nach dem Senden des 
ersten Datenbytes nur den bit ADDR gesetzt. Wieso? sollte es nicht 
automatisch resettet werden und bei der nächsten Übertragung das TXE-bit 
gesetzt werden?

: Bearbeitet durch User
von Thomas E. (picalic)


Lesenswert?

Viktor B. schrieb:
> Irgendwie springt es
> von der Zeile          if(I2C_num_bytes){                     // in case
> its available
> bei I2C_num_bytes = 0 anstatt zur nächsten schließenden geschweiften
> Klammmer einfach völlig aus dem Interrupt.

Wieso? Ist doch richtig: In dem Interrupt ist ja auch nichts mehr zu 
tun!
Geschweifte Klammern sind keine Sprungmarken, sondern kennzeichnen nur 
logische Programmblöcke. Ob und wie der Compiler dafür Code erzeugt, 
bleibt ihm überlassen.

: Bearbeitet durch User
von Viktor B. (coldlogic)


Lesenswert?

Thomas E. schrieb:
> In dem Interrupt ist ja auch nichts mehr zu
> tun!

Ich hätte erwartet, dass das Programm den IF-Block, der in die Klammern 
eingeschlossen ist, überspringt und mit
1
else if((I2C1->SR1 & I2C_SR1_BTF))
weitermacht?..

von Bernd K. (prof7bit)


Lesenswert?

Viktor B. schrieb:
> Compiler hinter EmBitz erkennt zwar andere Definitionen aus der selben
> Datei, aber genau die eine irgendwie nicht. Was soll man da bloß machen?

Sie ohne Tippfehler zu schreiben wär spontan mein erster Gedanke.

von Frank B. (f-baer)


Lesenswert?

Viktor B. schrieb:
> Thomas E. schrieb:
>> In dem Interrupt ist ja auch nichts mehr zu
>> tun!
>
> Ich hätte erwartet, dass das Programm den IF-Block, der in die Klammern
> eingeschlossen ist, überspringt und mit
>
1
else if((I2C1->SR1 & I2C_SR1_BTF))
> weitermacht?..

Dafür solltest du deine geschweiften Klammern mal sortieren.
Du hast hier zwei verschachtelte If-Blöcke...

von Viktor B. (coldlogic)


Angehängte Dateien:

Lesenswert?

Mein Logic Analyser ist endlich da. Bringt nur mäßig Licht ins Dunkle.

Der µC weiß nichts mit der Clock anzufangen?...

Noch interessanter: Ich hab den Code umsortiert, nun sieht die Routine 
so aus:
1
void I2C1_EV_IRQHandler(){                                              // Self-containing I2C interrupt
2
        if(I2C1->SR1 & I2C_SR1_SB)                                          // triggered by start condition ->
3
        {
4
            I2C1->DR = I2C_pointer->I2C_addr;                               // send I2C adress
5
        }
6
        else if((I2C1->SR1 & I2C_SR1_ADDR)||(I2C1->SR1 & I2C_SR1_TXE)||(I2C_pointer->I2C_num_bytes > 0))// if not, then send data
7
        {
8
            I2C1->DR = *(I2C_pointer->I2C_reading_pointer);
9
            I2C_pointer->I2C_reading_pointer++;
10
            I2C_pointer->I2C_num_bytes--;
11
        }                                                                   // if not - send stop condition
12
        else if((I2C1->SR1 & I2C_SR1_BTF))
13
        {
14
            I2C1->CR1 |= ((uint16_t)0x0200);                                // EmBitz does not recognize CR1_STOP_Set
15
            I2C_pointer->I2C_errorcode = 0;
16
            I2C_pointer->I2C_lock = 0;
17
        }
18
        else {
19
            GPIO_WriteBit(GPIOC, ((uint16_t)0x2000), Bit_RESET);
20
        }
21
    }

An dem Signalverlauf ändert sich nichts, dafür geht der µC regelmäßig in 
einen Hard Fault.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

> Bringt nur mäßig Licht ins Dunkle.

Das sehe ich aber ganz anders. Wir können hier sehen, dass ein top 
sauberes Timing vorliegt, dass der Master die Zahl 0x78 sendet (= 7bit 
Adresse 0x3C), und dass der Slave diese mit ACK bestätigt. Dass heisst, 
der Slave hat reagiert, er fühlt sich angesprochen.

Aber die nachfolgenden Bytes und das abschließende das Stop Signal 
(roter Punkt) fehlen. Sieht für mich 100% nach einem Softwarefehler auf 
dem Master aus.

Bau mal in deine vier if/else Abschnitte Trace Meldungen 
(http://stefanfrings.de/stm32/index.html#traceswo) ein um zu sehen, 
welche davon in welcher Reihenfolge ausgeführt werden.

von Viktor B. (coldlogic)


Lesenswert?

Stefanus F. schrieb:
> Bau mal in deine vier if/else Abschnitte Trace Meldungen
> (http://stefanfrings.de/stm32/index.html#traceswo) ein um zu sehen,
> welche davon in welcher Reihenfolge ausgeführt werden.

Ich hab so schnell keine Möglichkeit gefunden, Trace Meldungen in EmBitz 
zu lesen, deswegen musste das Debugging per LED (und Logic Analyser) 
erfolgen. Der Interrupt schien sich sinnlos zu wiederholen. Beim 
Studieren des Datenblattes ist mir dann eine Zeile aufgefallen:

> As soon as the address byte is sent,the ADDR bit is set by hardware
> and an interrupt is generated if the ITEVFEN bit is set.
> Then the master waits for a read of the SR1 register
> *followed by a read of the SR2 register*

Als ein Experiment wurde also die ISR abgeändert. Folgende Zeile wurde 
hinzugefügt:
1
else if(((I2C1->SR1 & I2C_SR1_ADDR)||(I2C1->SR1 & I2C_SR1_TXE))&&(I2C_pointer->I2C_num_bytes > 0))// if not, then send data
2
        {
3
            if(I2C1->SR2&I2C_SR2_BUSY)                     //seems crucial
4
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
5
            I2C1->DR = *(I2C_pointer->I2C_reading_pointer);
6
            I2C_pointer->I2C_reading_pointer++;
7
            I2C_pointer->I2C_num_bytes--;
8
        }

Und siehe da, die ganze Initialisierungssequenz wird übertragen. Zwar 
hab ich komischerweise Schnee im Display, aber es ist besser als gar 
nichts. LA sagt, die zweite Übertragung endet mit einer Stop Condition 
genau nach der Start Condition. Da muss ich wohl nach anderen Fehlern 
suchen.

von Viktor B. (coldlogic)


Lesenswert?

Update: der zweite Schreibvorgang (foo) klappt bestens, wenn ich das 
Programm mit dem Debugger Schritt für Schritt durchgehe. Dafür, wenn ich 
das Programm laufen lasse, wird nur ein Start und gleich darauf ein Stop 
gesendet..? WTF?

von Lama (Gast)


Lesenswert?

Schaue dir doch mal die Bits im SR mal ganz genau an :-)

von Viktor B. (coldlogic)


Lesenswert?

Lama schrieb:
> Schaue dir doch mal die Bits im SR mal ganz genau an :-)

Welches SR von der beiden?
Im Datenblatt oder während der Programmausführung?
Welche Bits müssten denn Ihrer Meinung nach genau angeschaut werden?

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.