Forum: Mikrocontroller und Digitale Elektronik Problem mit I²C (CubeMX)


von Harry L. (mysth)


Lesenswert?

Hallo,
ich hab ein sehr seltsames Problem mit den I²C-Funktionen des 
HAL-Library.
Ich nutze den DMA-Mode für die I²C-Schnittstelle.
An der Schnittstelle hängt ein 0,96" Oled-Display mit einem SSD1306.
Pullup-Widerstände (1k2 bei 3,3V) sind auch bestückt.
Das Board ist ein MapleMini mit ST32F103C8-CPU.

Der Transfer selbst ist auch gar kein Problem, und bevor ich ein neues 
Datenpaket absende, überprüf ich zunächst mit "HAL_I2C_GetState (&hi2c1) 
== HAL_I2C_STATE_READY" ob der Buffer frei ist.

Das funktioniert so auch, und die Initialisierung läuft auch wie 
erwartet.

In der Initialisierung läuft das noch geblockt, und ich warte vor jedem 
Transfer darauf, daß der Treiber READY liefert.

Die Funktion void oled_worker() wird bei jedem Durchlauf der main-loop 
einmal aufgerufen, und bei jedem Aufruf wird für eine Row im Display 
(128 Byte entspr. 8 Zeilen zu 128Pixel) geprüft, ob sich was am 
Pufferinhalt geändert hat, und ggf. der Transfer über I²C angestossen 
(max 128 Byte + 7 Befehlsbyte).

Vor dem eigentlichen Senden prüfe ich wieder ob der Puffer frei ist.
Wenn das nicht der Fall ist, wird die Funktion beendet, (non-blocking) 
und beim nächsten Aufruf wiederholt sich das Spiel bis der Puffer frei 
ist.

Jetzt wirds allerdings spannend:
Wenn ich mir bei dem diesem Transfer-Kommando (ziemlich am Ende der 
Datei) einen Breakpoint setze, und dann manuell fortsetze, läuft es wie 
erwartet, aber wenn ich das Programm einfach starte, wird Müll auf dem 
Display ausgegeben.

Es hat etwas Mühe gekostet, das Problem auf genau diesern Punkt 
einzugrenzen.

Wenn ich -testweise- nach dem Send-Command ein HAL_delay() einfüge muß 
das Delay min. 4 sein (zw. 3 und 4ms) damit das Programm fehlerfrei 
läuft.
Wenn ich das Delay auf 3 stelle (zw. 2 und 3ms), kommen wieder falsche 
Zeichen.
Der Transfer einer Zeile dauert ca. 2,7ms, was sich mit dieser Wartezeit 
deckt.

Ich vermute also, daß das Send-Kommand bereits aufgerufen wird, obwohl 
noch ein Transfer läuft.

Ist es möglich, daß HAL_I2C_GetState() mir falsche Daten liefert, oder 
hab ich irgendwas übersehen?

Harry
1
/*
2
 * oled_worker.c
3
 *
4
 *  Created on: 17.04.2016
5
 *      Author: harry
6
 */
7
8
#include <string.h>
9
#include <stdlib.h>
10
#include "stm32f1xx_hal.h"
11
#include "oled.h"
12
#include "needle.h"
13
14
#define  OLED_IDLE  0
15
#define  OLED_UPD_PAGE  1
16
#define  OLED_UPD_LINE  2
17
#define  OLED_NEEDLE_MOV  3
18
#define  OLED_VAL_CHG  4
19
#define OLED_LINE_PREF  5
20
21
#define NEEDLE_IDLE  0
22
#define NEEDLE_UPDATE  1
23
#define NEEDLE_
24
//#define  OLED_
25
//#define  OLED_
26
27
#define WRK_STAT_IDLE  0
28
#define WRK_STAT_UPDATE  1
29
#define WRK_STAT_UPD_LINE  2
30
#define WRK_STAT_UPD_COL 3
31
//#define WRK_STAT_
32
//#define WRK_STAT_
33
34
static const uint8_t OledInit[] =
35
  { 0x80, 0xae,    // turn off oled-display
36
      0x80, 0xd5,0x90,  // set display clock divide ratio/oscillator frequency
37
      0x80, 0xa8,0x3f,  // set multiplex ratio(1 to 64)
38
          // 1/64 duty
39
      0x80, 0xd3,0,00,  // set display offset
40
      // not offset
41
      0x80, 0x8d,0x14,  // set Charge Pump enable/disable
42
      0x80, 0x40,  // set start line address
43
      0x80, 0xa6,  // set normal display
44
      0x80, 0xa4,  // Disable Entire Display On
45
      0x80, 0xa1,  // set segment re-map 128 to 1
46
      0x80, 0xc8,  // Set COM Output Scan Direction 64 to 1
47
      0x80, 0xda,0x12,  // set com pins hardware configuration
48
      0x80, 0x81,0xf1,  // set contrast control register
49
      0x80, 0xdb,0x40,  // set vcomh
50
      0x80, 0xaf  // turn on oled panel
51
52
    };
53
54
uint8_t oled_state;
55
uint8_t oled_page;
56
uint8_t needle_last_val;
57
uint8_t needle_val;
58
uint8_t needle_state;
59
uint8_t worker_state;
60
uint8_t update_request;
61
uint8_t needle_pos = 0;
62
63
64
uint8_t cmd_buf[6];
65
66
67
#define MAX_CMD_LEN  7
68
#define PREFIX_LEN  7
69
#define SET_OLED_ADR(page,col)  oline_buf.cmd[1]= 0xb0 | page; \
70
        oline_buf.cmd[3] = (col >> 4) | 0x10; \
71
        oline_buf.cmd[5] = col & 0x0f;
72
73
uint8_t oled_buf[8][0x80];
74
75
76
struct
77
{
78
  uint8_t cmd[MAX_CMD_LEN];
79
  uint8_t line[0x80];
80
  uint8_t l;
81
  uint8_t r;
82
  uint8_t size;
83
} oline_buf;
84
85
void
86
oled_init (void)
87
{
88
  uint8_t i, j;
89
  HAL_Delay (1000);
90
  while (HAL_I2C_GetState (&hi2c1) != HAL_I2C_STATE_READY)
91
    ;
92
  HAL_I2C_Master_Transmit_DMA (&hi2c1, OLED_ADR, OledInit, sizeof(OledInit));
93
  oline_buf.cmd[0] = oline_buf.cmd[2] = oline_buf.cmd[4] = 0x80;
94
  oline_buf.cmd[6] = 0x40;
95
worker_state = WRK_STAT_IDLE;
96
97
  // clear PicBuf
98
  for (j = 0; j < 0x80; j++)
99
    {
100
      oline_buf.line[j] = 0x00;
101
    }
102
103
  for (i = 0; i < 8; i++)
104
    {
105
      for (j = 0; j < 0x80; j++)
106
  {
107
    oled_buf[i][j] = 0x00;
108
  }
109
      while (HAL_I2C_GetState (&hi2c1) != HAL_I2C_STATE_READY)
110
  ;
111
      SET_OLED_ADR(i, 0);
112
      HAL_I2C_Master_Transmit_DMA (&hi2c1, OLED_ADR, oline_buf.cmd, 128 + PREFIX_LEN);
113
   }
114
}
115
116
void
117
oled_worker (void)
118
{
119
  uint8_t i, j, q;
120
  static uint8_t lc;
121
122
  switch (worker_state)
123
    {
124
    case WRK_STAT_IDLE:
125
      if (lc > 7)
126
  {
127
    lc = 0;
128
  }
129
130
      oline_buf.l = 0x80;
131
      oline_buf.r = 0;
132
      oline_buf.size = 0;
133
134
      q = 0;
135
      for (j = 0; j < 128; j++)
136
  {
137
    if (q == 0)
138
      {
139
        if (lc < 6)  // Needle only in top 6 rows!
140
    {
141
      if (oled_buf[lc][j]
142
          != (PicBuf[lc][j] & needle_buf[lc][j]))
143
        {
144
          oline_buf.l = j;
145
          q = 1;
146
          i = 0;
147
        }
148
    }
149
        else
150
    {
151
      if (oled_buf[lc][j] != PicBuf[lc][j])
152
        {
153
          oline_buf.l = j;
154
          q = 1;
155
          i = 0;
156
        }
157
    }
158
      }
159
    if (q == 1)
160
      {
161
        if (lc < 6)  // with meter-mask
162
    {
163
      if (oled_buf[lc][j]
164
          != (PicBuf[lc][j] & needle_buf[lc][j]))
165
        {
166
          if (j > oline_buf.r)
167
      {
168
        oline_buf.r = j;
169
      }
170
        }
171
          oled_buf[lc][j] = oline_buf.line[i++] = PicBuf[lc][j]
172
        & needle_buf[lc][j];
173
    }
174
        else
175
    {
176
      if (oled_buf[lc][j] != PicBuf[lc][j])
177
        {
178
          if (j > oline_buf.r)
179
      {
180
        oline_buf.r = j;
181
      }
182
        }
183
          oled_buf[lc][j] = oline_buf.line[i++] = PicBuf[lc][j];
184
    }
185
      }
186
  }
187
188
      if (oline_buf.l != 0x80)  // line needs some update
189
  {
190
    oline_buf.size = oline_buf.r - oline_buf.l + 1;
191
    worker_state = WRK_STAT_UPD_LINE;
192
  }
193
      else  // advance to next line
194
  {
195
    lc++;
196
  }
197
198
      break;
199
200
      /*
201
       *
202
       */
203
204
    case WRK_STAT_UPD_LINE:
205
      if (HAL_I2C_GetState (&hi2c1) == HAL_I2C_STATE_READY)
206
  {
207
    SET_OLED_ADR(lc,oline_buf.l);
208
    HAL_I2C_Master_Transmit_DMA (&hi2c1, OLED_ADR, oline_buf.cmd,
209
               oline_buf.size + PREFIX_LEN);
210
211
/*
212
 * Wenn ich hier 4ms Delay einfüge, läuft alles
213
 */
214
    HAL_Delay(4);
215
    oline_buf.l = 128;
216
    oline_buf.r = 0;
217
    lc++;    // advance to next line
218
    worker_state = WRK_STAT_IDLE;
219
220
  }
221
      break;
222
    }
223
}

von NACK (Gast)


Lesenswert?

Kann es sein, dass du dem DMA über seine Daten trampelst wärend er noch 
läuft und deshalb das delay einen heilenden Effekt hat?

Entweder sehe ich es nicht, aber für mich schaut es so aus, als ob 
oline_buf... beim nächsten Aufruf von oled_worker geschrieben wird bevor 
getested wird ob der DMA noch aktiv ist.

Gruß

von fop (Gast)


Lesenswert?

Dazu müsste man die Daten auf dem Bus mal sehen. Was ich auch schon 
hatte, sind IC's deren Schnittstelle schneller ist als ihre interne 
Logik. Sprich es kann auch sein, dass das Display 0,3 ms benötigt, um 
die Daten, die vom I2C herein kamen zu verdauen.

von Harry L. (mysth)


Lesenswert?

NACK schrieb:
> Kann es sein, dass du dem DMA über seine Daten trampelst wärend er noch
> läuft und deshalb das delay einen heilenden Effekt hat?

Ja, danach sieht es aus, aber es gibt keine weiteren I²C-Aufrufe im 
übrigen Programm.

Ich hab das Programm bereits auf die oben gezeigten Funktionen 
reduziert.
Es wird garantiert an keiner anderen Stelle geschrieben.

fop schrieb:
> Dazu müsste man die Daten auf dem Bus mal sehen. Was ich auch schon
> hatte, sind IC's deren Schnittstelle schneller ist als ihre interne
> Logik. Sprich es kann auch sein, dass das Display 0,3 ms benötigt, um
> die Daten, die vom I2C herein kamen zu verdauen.

Das hab ich mir angeschaut, und ja, es ist so, daß zu früh gesendet 
wird.
Aber warum?
Das würde bedeuten, daß die HAL_I2C_GetState nicht richtig funktioniert.
Es geht auch nicht um 0,3ms, sondern um min. 3 ms, was darauf hindeutet, 
daß ich bereits sende, während die Schnitstelle noch aktiv ist.
Harry

von NACK (Gast)


Lesenswert?

Harry L. schrieb:
> Ja, danach sieht es aus, aber es gibt keine weiteren I²C-Aufrufe im
> übrigen Programm.

Ja schon, aber oled_worker selbst könnte im nächsten Zyklus über die 
Daten schreiben die noch nicht komplett gesendet wurden. Probier doch 
mal
"if (HAL_I2C_GetState (&hi2c1) == HAL_I2C_STATE_READY)" direkt am Anfang 
zu testen bevor du irgendwas in oled_worker machst.

von Harry L. (mysth)


Lesenswert?

oled_worker() -am Ende- ist aber der einzige Ort, an dem ich auf das 
I²C-Subsystem zugreife.
Natürlich könnte ich direkt am Anfang prüfen, aber genau da wollte ich 
das ja nicht, da an der Stelle die meiste Rechenzeit gebraucht wird (der 
Vergleich der Puffer) und das kann wunderbar geschehen, während bereits 
ein Transfer läuft.

Harry

von NACK (Gast)


Lesenswert?

Harry L. schrieb:
> oled_buf[lc][j] = oline_buf.line[i++] = PicBuf[lc][j]
>         & needle_buf[lc][j];

Ich habe jetzt nicht versucht die ganze Logik nachzuvollziehen, aber 
kann es sein, dass du den Puffer hier schon "wieder" beschreibst, 
während noch ein Transfer läuft?

Ansonsten, probier doch einfach mal den Test an den Anfang zu schieben 
und schau obs dann ohne delay geht.

Nur so

von Harry L. (mysth)


Lesenswert?

NACK schrieb:
> Ich habe jetzt nicht versucht die ganze Logik nachzuvollziehen, aber
> kann es sein, dass du den Puffer hier schon "wieder" beschreibst,
> während noch ein Transfer läuft?


Oh Mann!


Das wars!


Manchmal ist man einfach betriebsblind

Herzlichen Dank!

Da muss ich wohl mit 2 alternierenden Puffern arbeiten.

Harry

von NACK (Gast)


Lesenswert?

Prost! :)

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.