Forum: Mikrocontroller und Digitale Elektronik I2C auf dem STM32 F411RE


von Framlin (wolfgang_e34)


Lesenswert?

Ich bin blutiger Anfänger, also verzeiht mir bitte die Frage .....

Ich möchte mit meinem Nucleo-F411RE einen Kompass mit I2C abfragen.
Ich möchte das machen, indem ich Register setze und möchte dabei keinen 
CubeMX-generierten Code benutzen, möglichst auch keine HAL-Funktionen. 
(Ich implementiere OOP/C++ - Code)

Ich hab ein Beispiel gefunden, bei dem ein F0 verwendet wird. Dort wird 
u.a. das Register I2C1->TIMINGR gesetzt, nachdem der Wert zuvor mittels 
eines Tools von ST berechnet wird.
Das Tool gibt es für F4 nicht und im Referenzhandbuch des F411RE ist 
dieses Register nicht aufgeführt.
Weiß jemand, ob das ein Fehler des Referenzhandbuches ist, oder ob man 
das beim F4 anders handhaben muss oder wo es steht (falls ich nur zu 
doof bin, es zu finden)?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Der F0 und der F4 haben eine komplett andere I²C-Peripherie. Beim F4 
braucht's das Tool für die Timing-Berechnung nicht.

Wolfgang E. schrieb:
> Ich bin blutiger Anfänger
Dann solltest du die HAL benutzen... Gerade die I²C-Peripherie der alten 
STM32-Controller (F1, F4) ist ziemlich umständlich, und die ohne HAL zu 
nutzen ist ziemlich lästig. Bei den neueren Controllern (F0, L0, F7) ist 
das deutlich einfacher, braucht aber das genannte Tool.

von Framlin (wolfgang_e34)


Lesenswert?

@Niklas G.
Ok, danke! dann weiß ich schon mal, dass mir das F0 - Beispiel wenig 
nutzt ;)

Und ja, das mit der Umständlichkeit kann schon sein, aber ich hab 
geschrieben, dass ich HAL nicht benutzen will, weil ich es schon 
ausprobiert habe und zu dem Ergebnis gelangt bin, dass es zu meiner Art 
zu denken und zu programmieren einfach nicht passt. Ich komme mit 
Registern besser klar.

Jeder findet zu unterschiedlichen Paradigma unterschiedliche Zugänge. 
Für mich sind CubeMX und HAL ungeeignet, auch wenn das dann an der ein 
oder andere Ecke lästig sein mag.

von Johnny B. (johnnyb)


Lesenswert?

Wolfgang E. schrieb:
> Für mich sind CubeMX und HAL ungeeignet

Für Dich vielleicht ungeeignet, aber der Code für I2C, welcher mit 
CubeMX erzeugt wird, funktioniert nun mal "out of the box".
Also entweder Du hangelst Dich selbst durchs Manual Deines Controllers 
durch oder Du erzeugst ein Projekt mit CubeMX/HAL und vergleichst es 
dann mit Deinem Code, bis Dein eigener Code dann dasselbe macht und auch 
funktioniert.

von Framlin (wolfgang_e34)


Lesenswert?

@Jonny B.
Oder es findet sich jemand, der mir einen "Link" zu nem 
Register-Beispiel-Code gibt, das ich dann an meine Bedürfnisse anpassen 
kann.

Nach meinen bisherigen Erfahrungen ist das der Weg, der den geringsten 
Aufwand macht und bei dem ich am meisten lerne ....

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Les doch einfach mal das Reference Manual zu deinem STM32.
Da wirste sogar an die Hand genommen wie die Register beschrieben werden 
müssen und dannn wird jedes Bit erklärt.

Sich das rückwärts aus fertigen Registerklimpercode auszulesen soll dich 
weiter bringen?
naja.

von Framlin (wolfgang_e34)


Lesenswert?

@Mw E:
Ähm, ja, wie eingangs geschrieben, hab ich das Reference Manual (u.a. 
den I2C-Abschnitt) ja schon gelesen. Daher ist mir ja auch aufgefallen, 
dass da nirgends was von nem I2Cx->TIMINGR steht. Ich vermute mal stark, 
dass statt dessen das I2Cx->I2C_CCR dafür zuständig ist.
Aber manchmal sind ja auch Reference Manuals fehlerhaft oder 
Beispielcode oder beides oder meine Auffassungsgabe, daher hab ich hier 
mal nachgefragt und ja auch ne sinnvolle Antwort bekommen.

Abgesehen davon ist die detaillierte Beschreibung der Register alleine 
nur die halbe Miete. Informativ wär da noch die Beschreibung des 
Algorithmus, also BeispielCode oder dergl. wie er ja für die F0/F3 
offenbar zur Verfügung gestellt wird.
Ich kann mir zwar vorstellen, dass man sich, wenn man das sehr sehr 
gründlich durcharbeitet und sich sehr intensiv damit beschäftigt, den 
Algorithmus auch selber erarbeiten kann, aber da ich grundsätzlich faul 
bin, wär ich dankbar gewesen, wenn ST (oder ein YouTuber oder ein 
Blogautor oder ...) mir das schon mal als TLTR zusammengefasst hätte ;)

Zumindest mir hilft es, wenn ich (gut kommentierten) 
Registerbeispielcode hab. Den kann ich dann als Ausgangspunkt dafür 
nehmen, im Reference Manual nachzuschauen, wie ich das dann an meine 
Bedürfnisse anpassen kann. Das hilft mir auch, das Reference Manual 
besser zu verstehen.
Da hat eben jeder so seinen Weg, wodurch er am besten lernt .....

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Da oben haste dermaßen F0 und F4 durcheinandergewühlt, dass man nicht 
davon ausgehen kann, dassus richtig gelsen hast.

Auch jetzt sprichste wieder nur von Registern und, dass es kein 
"Algorithmus" gibt.

Bevor die Register beschrieben werden gibt es eine ellenlange Erklärung 
wie auf dnene rumzuklimpern ist.
Davor wird sogar mal noch der I2C erklärt.
-> Also hastes nicht richtig gelesen!

ALso les das dann nochmal und wenn dann noch Fragen aufkommen ist der 
Thread hier richtig dafür.

STM32F411 refman Kaoitel 18.3 willst du lesen.
(18.3.2/18.3.7 kannste ignorieren)

: Bearbeitet durch User
von Framlin (wolfgang_e34)


Lesenswert?

@Mw E:
Was ich gesucht hatte, war zusätzlich zum Reference Manual so was hier :
https://sites.google.com/site/johnkneenmicrocontrollers/18b-i2c/i2c_stm32f407

;-)

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Also bei sowas bekomm ich nen Schreikrampf:
1
RCC->APB1ENR |= 1 <<21;
2
while (!(I2C1->SR1 & 0x0002));
Die Bits haben Namen...

von Gerd E. (robberknight)


Lesenswert?

Wolfgang E. schrieb:
> also BeispielCode oder dergl. wie er ja für die F0/F3
> offenbar zur Verfügung gestellt wird.

Hier gäbe es Beispielcode, passend für die F4er:
https://github.com/ChibiOS/ChibiOS/tree/master/os/hal/ports/STM32/LLD/I2Cv1

Das ist aus dem HAL von ChibiOS. Das hat nix mit dem (meiner Meinung 
nach grauenhaften) HAL oder Cube von ST zu tun

von Framlin (wolfgang_e34)


Lesenswert?

Ich seh keinen Sinn darin, Bits zu benennen.
Ich will schreiben können:
LED led(0,5);
led = ON;

Und dafür sind die konkreten Bennungen, wie sie ST in der HAL für Ports 
und Pins usw anbietet eher kontraproduktiv. Ich will mir „ausrechnen“ 
können, wohin ich welches Bit shifte und keine „hardcodierten“ GPIOA 
oder TIM5_1 oder was auch immer setzen ....

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Wenn man ein Statusbit abfragt will man aber schon wissen welches.
Bit2 hat da sehr viel Aussagekraft (Achtung Ironie).
Trotzdessen, dass ich mir meine eigenen Treiber geschrieben habe für 
STM32, weis ich jetzt nicht welches Stazusbit Bit2 war, bei nem Namen 
fällts einem sofort wieder ein.

Ich finde den ST HAL genauso wie GerdE einfach nur grauenhaft.
Letztens musste ich ihn mal nutzen,w eil ich mir nen USB Host nicht 
selber schreiben wollte.
Direkt mal 3 Bugs entdeckt die mich nen Tag gekostet haben.

Was is an GPIOA jetz so schlecht?
Der heißt eben so, der Pin auch.
Den als 1 zu enhemn wäre sehr kontraproduktiv.

Hier was aus meinem Treiber zu GPIO:
1
rcc_enable_clock(RCC_GPIOA);
2
gpio_initpin(PORTA, 5, AF_PP, GPIO_HS, AF5_SPI1_TO_2);  //CLK   (SPI1)
3
4
void i2c_init(unsigned int i2c_base, unsigned int apb_clk, unsigned int i2c_clk){
5
6
  volatile struct i2c * const i2c = (volatile struct i2c *)i2c_base;
7
  
8
  //Freaky Taktgedöhns, siehe Datenblatt!
9
  i2c->CR2 = apb_clk/1000000;
10
  i2c->CCR = apb_clk/i2c_clk/2;
11
  i2c->TRISE = (apb_clk/1000000) + 1;
12
  
13
  //einschalten
14
  i2c->CR1 = CR1_PE;
15
};
16
17
//gemeinsamer Teil für start und rep_start
18
static uint8_t starting(unsigned int i2c_base, uint8_t addr){
19
20
  volatile struct i2c * const i2c = (volatile struct i2c *)i2c_base;
21
  
22
  //AF Bit löschen falls es mal nen Fehler gab
23
  i2c->SR1 &= ~SR1_AF;
24
25
  //Start in Auftrag geben und auf Aussendung warten
26
  i2c->CR1 |= CR1_START;
27
  while (0 == (i2c->SR1 & SR1_SB));
28
  
29
  //danach darf die Adresse gesendet werden
30
  i2c->DR = addr;
31
32
  //warten bis die Adresse gesendet wurde, bei NACK ist dann Schluss
33
  unsigned int sr1;
34
  do{
35
    sr1 = i2c->SR1;
36
    
37
    //Endlosschleife verhindern wenn kein ACK auf die Adresse folgt
38
    if (sr1 & SR1_AF){
39
      i2c->CR1 |= CR1_STOP;
40
      while(i2c->SR2 & SR2_MSL);
41
      return 1;
42
    }
43
  }while(0 == (sr1 & SR1_ADDR));
44
  
45
  //es muss unbedingt SR2 gelesen werden um ADDR auf 0 zu setzen
46
  volatile unsigned int sr2 = i2c->SR2;
47
  (void)sr2;
48
  
49
  return 0;
50
}
51
 
52
uint8_t i2c_start(unsigned int i2c_base, uint8_t addr){
53
54
  volatile struct i2c * const i2c = (volatile struct i2c *)i2c_base;
55
  
56
  if (i2c->SR1 & SR1_BERR){
57
    i2c_reset(i2c_base);
58
  }
59
  
60
  //beim ersten Start warten bis keiner mehr auf Bus rumspielt
61
  uint16_t waited = 0;
62
  while(i2c->SR2 & SR2_BUSY){
63
    waited++;
64
    if (waited > 10000){
65
      i2c_reset(i2c_base);
66
    }
67
  }
68
  return starting(i2c_base, addr);
69
}

von Gerd E. (robberknight)


Lesenswert?

Wolfgang E. schrieb:
> Ich seh keinen Sinn darin, Bits zu benennen.

für Deine eigentliche Applikation ja. Für die Implementation einer 
Hardware-Abstraktionsschicht macht das aber Sinn sowas zu haben. Vor 
allem wenn die Hardwarehersteller schon fertige Header mit den Defines 
liefern.

> Ich will schreiben können:
> LED led(0,5);
> led = ON;

Wirklich?

Eigentlich definiert man eher ein Boardfile oder ähnliches, welches die 
Zuordnung zwischen einzelnen GPIOs, UART-Einheiten,... und abstrakten 
Namen macht. Dann verwendest Du in Deiner eigentlichen Applikation nur 
den Namen.

Hier ein Beispiel wie das mit dem ChibiOS-HAL geht:
1
 while (true) {
2
    palClearLine(LINE_LED_GREEN);
3
    chThdSleepMilliseconds(500);
4
    palSetLine(LINE_LED_GREEN);
5
    chThdSleepMilliseconds(500);
6
}

von Framlin (wolfgang_e34)


Lesenswert?

Das ist eben der Unterschied. Ich will das wirklich so haben ;)

Ich will ja auch keinen HAL schreiben, sondern ne Anwendung.

Ich hab ein konkretes Board, drei Servos, zwei Schrittmotoren und fünf 
Sensoren, zwei kommunizieren via SPI, drei via I2C.

Daher hab ich 10 Pins, drei davon sind PWMPins die haben PWMTimer usw.
Da möchte ich schreiben können
1
Servo steerig(STEERING_PORT, STEERING_PIN);
2
Compass compass(COMPASS_PORT, COMPASS_PIN);
3
4
steering.degree(compass.direction() + 180);

Insofern machen Namen schon Sinn und auch ein include-file, in dem man 
die Zuordnung „konfigurieren“ kann.

Was ich meine, sind aber die darunter liegenden „Schichten“. Ein Servo 
ist ein PWMPin und das ist ein spezialisierter GPIOPin. Bestimmte 
Initialisierungen sind dabei für alle Pins und alle Ports gleich und da 
möchte ich dann nicht die „hardcodierten“ Namen wie TIM4_CH3 oder GPIOB 
verwenden, sondern da möchte ich anhand von in Argumenten übergebenen 
Zahlen berechnen, an welcher konkreten Stelle im Register ich welches 
Bit setze.
Und ganz „oben“ möchte ich auch keine ST-Datentypen wie 
GPIOInitStructure oder so haben, sondern eigene Abstraktionstypen oder 
Zahlen und Strings.

: Bearbeitet durch User
von Gerd E. (robberknight)


Lesenswert?

Wolfgang E. schrieb:
> Das ist eben der Unterschied. Ich will das wirklich so haben ;)

Ich bezog mich da vor allem auf Deine "LED"-Klasse, die so aussieht, als 
würde sie mit Portnummern und Pins initialisiert, die sie demnach wohl 
auch irgendwo im RAM speichern muss.

Du hast hier kein System mit ewig viel RAM und vor allem keine CPU mit 
einer MMU, die Dir aus kleinen verstreuten Pages einen schönen linearen 
Adressraum zaubert. Auch wenn Du Dir wegen den 8 Bytes RAM erst mal 
keine Sorgen machen musst, so sollte man sich dennoch mit dem Thema 
RAM-Allokation (dynamisch vs. statisch) und vor allem Fragmentierung 
frühzeitig auseinandersetzen.

Wenn möglich sollte man daher die Zuordnung zur compile-Zeit machen. 
Also mit defines oder consts, nicht mit Klassen aus dem RAM.

: Bearbeitet durch User
von Framlin (wolfgang_e34)


Lesenswert?

@Gerd E.
Oh, das war mir nicht bewusst. Danke! für den Hinweis.
Bedeutet das, dass man C++ auf uCs nicht benutzen kann?
Hast Du zufällig gerade „Literaturstellen“ zu dem Thema im Kopf?

von Framlin (wolfgang_e34)


Lesenswert?

@Mw E.:
Mw E. schrieb:
> ALso les das dann nochmal und wenn dann noch Fragen aufkommen ist der
> Thread hier richtig dafür.

Ok, ich hab gelesen und gecodet und nun hab ich Fragen ;)

Ich versuche ein Byte von einem Accelerator zu lesen. Auf oberster Ebene 
schaut das so aus:
1
  unsigned char result;
2
  START();
3
  ADDR(LSM303DLHC_SLAVE_ADDR);
4
  WRITE(LSM303DLHC_REG_L_ADDR);
5
  WRITE(LSM303DLHC_REG_H_ADDR);
6
  START();
7
  ADDR(LSM303DLHC_SLAVE_ADDR|1);
8
  result = READ(0);
9
  STOP();
10
  return result;


Da in RefManual von dem Accelerator folgendes steht:
"The slave address is completed with a Read/Write bit. If the bit is ‘1’ 
(read), a repeated START (SR) condition must be issued after the two 
sub-address bytes;"
hab ich beide Adressbytes des Registers, das ich lesen will, 
hintereinander geschickt.


Das Programm kommt bis zum Lesen, d.h das letzte ADDR funktioniert noch, 
das READ dann nicht mehr
1
unsigned char I2CPort::READ(int ack) {
2
3
  if (ack){
4
    I2C1->CR1 |= I2C_CR1_ACK; //multiple bytes - set acknowledge bit in I2C_CR1
5
  } else {
6
    I2C1->CR1 &= ~I2C_CR1_ACK; //single or last byte clear ack bit
7
  }
8
9
  while (!(I2C1->SR1 & I2C_SR1_RXNE));// Wait until RxNE bit set - see status register
10
11
  return (I2C1->DR);
12
}
Das Programm wartet dann bis zum Sanktnimmerleinstag  auf das Setzen des 
RxNE bit.

Woran kann das liegen?

Ich vermute, dass das I2C grundsätzlich funktioniert, da das Programm ja 
sonst nicht so weit kommen würde, oder?

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Wie ist denn LSM303DLHC_SLAVE_ADDR definiert?

Beim Adresse senden auch schon mal geguckt ob ein Ack zurück kommt?
Is ja der Klassiker, dass die Adresse mal falsch ist und der I2C ins 
Nirvana schreit.
Das fällt dann beim lesen meist erst auf.

Andere Frage:
Haste auch den Takt eingeschalten für den I2C? Also über den RCC?
Ist der GPIO Pin auf "Alternate Function" zum I2C Initialisiert?

von Framlin (wolfgang_e34)


Lesenswert?

@Mw E.
#define LSM303DLHC_SLAVE_ADDR  0b00110010

Ich hab da schon testweise irgend nen Unsinn genommen, das scheitert 
dann sofort beim ersten Write.

Hier meine GPIO-Init
1
void I2CPort::initGPIO() {
2
//  we use PB6 -> I2C1_SCL
3
//  we use PB7 -> ISC1_SDA
4
5
//  1) Enabling the Alternate Function Clock
6
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
7
8
//  2) Enabling the clock to the GPIO used by the Alternate Function. PB6 Clock and PB7 Data are attached to I2C1)
9
  RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //clock to I2C1 RM0383 P.136
10
11
//  3) Enabling the clock to the Alternate Function in use
12
  //DataSheet Table 9: PB6 and PB7 -> I2C1 -> AF04
13
  //RM0383 P.163 P6 and P7 are on AFRL
14
  GPIOB->AFR[0] |=  (GPIO_AF4_I2C1 << (4*6)); //enable SCL to PB6
15
  GPIOB->AFR[0] |=  (GPIO_AF4_I2C1 << (4*7)); //enable SDA to PB7
16
17
//  4) Configuring the I/O pins used by the Alternate Function.
18
//  MODER Configure PB8 and PB9 (I2C1) as an AF output.
19
  GPIOB->MODER &= ~GPIO_MODER_MODER6_0;
20
  GPIOB->MODER |= GPIO_MODER_MODER6_1;
21
22
  GPIOB->MODER &= ~GPIO_MODER_MODER7_0;
23
  GPIOB->MODER |= GPIO_MODER_MODER7_1;
24
25
//  OTYPER Configure the pins PB6 & PB7 for I2C1 as open drain.
26
  GPIOB->OTYPER |= GPIO_OTYPER_OT_6;
27
  GPIOB->OTYPER |= GPIO_OTYPER_OT_7;
28
29
//  PUPDR Program the register to ensure that the pull up and pull down resitors for I2C1 are disabled.
30
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR6;
31
  GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR7;
32
}

Wie schau ich denn nach, ob ein ACK zurückkommt?
hier mein ADDR:
1
void I2CPort::ADDR(unsigned char addr) {
2
  char res;
3
  I2C1->DR = addr | 0;          //Write to I2C Address register
4
  while (!(I2C1->SR1 & I2C_SR1_ADDR));  //wait until address sent - see status register
5
6
  res = (I2C1->SR2);  //dummy read to clear - see status register 2
7
  if (res) res = 0;   //to avoid warning
8
}

: Bearbeitet durch User
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Im SR1 Register gibts das AF Bit (Acknowledge Failure), siehe mein Code 
oben.

Laut DaBla des Sensor brauchts nur 1 Byte als Subadresse und nicht 2.
Für Magnet und Beschleunigung auslesen gibt es 2 getrennte I2C Adressen, 
die haben da wohl einfach 2 ICs zusammengeklatscht.

Haste nen Oszi um mal nachzugucken was da passiert?

von Gerd E. (robberknight)


Lesenswert?

Wolfgang E. schrieb:
> Bedeutet das, dass man C++ auf uCs nicht benutzen kann?

Nein, natürlich kann man auch C++ sinnvoll auf µCs benutzen.

Nur sollte man sich vorher genau damit auseinandersetzen, was C++ 
implizit aus dem Code dann hinterher macht. Bei C ist das 
offensichtlicher.

Zum Thema Arbeitsspeicher generell:

Bei größeren CPUs lädst Du das Programm üblicherweise von der Festplatte 
oder Flash ins RAM und führst es dort aus. Der ganze Programmcode muss 
also ins RAM. Wenn Du den Code also einmal im RAM hast und mit ein paar 
Variablen zwischen verschiedenen Fällen umschaltest ist das optimal.

Bei µCs führst Du den Code normal direkt aus dem Flash aus und Du hast 
meist etwa eine Größenordnung mehr an Flash als RAM. Hier ist es also 
meist besser wenn Du mehrere verschiedene Varianten einer Funktion im 
Flash hast und dafür Variablen im RAM zum Umschalten zwischen denen 
einsparst.

Daher ist Dein Beispiel mit der LED-Klasse oben nicht optimal, besser 
wäre es das zur Compilezeit festzulegen und Port und Pin nicht im RAM 
abzulegen.

Das sind grobe, generelle Einschätzungen, es gibt natürlich immer Fälle 
bei denen es wieder anders aussieht.

Man kann in C++ natürlich auch sowas zur Compilezeit schon festlegen und 
dennoch OOP-Vorteile wie Ableitungen etc. nutzen. Das geht mit Templates 
und Template Metaprogramming. Damit muss man sich aber eingehend 
beschäftigen und in C++ wirklich sattelfest sein. Erschwerend kommt 
hinzu daß die Fehlermeldungen der Compiler im Bereich Templates meiner 
Erfahrung nach deutlich schwerer verständlich sind.

Ich nutze das daher nur ganz gezielt und punktuell wo es unterm Strich 
wirklich was bringt. Das ist nicht meine Standardvorgehensweise für eine 
08/15-LED-Klasse.

Zum Thema Speicherallokation und Fragmentierung:

Zur Fragmentierung kommt es, wenn Du mehrere Blöcke RAM allokierst, z.B 
so:

ABBBBBCDDDDDE

Dein Programm braucht jetzt nach einer Zeit z.B. B und D nicht mehr und 
gibt sie frei. A, C und E sind aber noch belegt. Dann hast Du:

A-----C-----E

Jetzt hast Du eigentlich 10 Blöcke Speicher frei. Dennoch kannst Du 
keinen zusammenhängenden Bereich mit 10 Blöcken mehr allokieren. Mit 
längerer Laufzeit kann das immer schlimmer werden, bis Du am Schluss nur 
noch kleine  Häppchen allokieren kannst und Dein Programm deswegen in 
out-of-memory läuft.

Bei größeren CPUs wie auf PCs hast Du eine MMU. Du kannst also zumindest 
auf Page-Ebene die freigegebenen Pages in anderer Reihenfolge 
wiederverwenden. Da musst Du Dir schon richtig Mühe geben bis das ein 
echtes Problem wird. Ein µC hat keine solche MMU und da wird das daher 
zum Problem.

Bei Embedded-Systemen die lange und zuverlässig laufen sollen ist die 
Speicherfragmentierung ein ernst zu nehmendes Problem. In vielen 
Anwendungen wird daher auf dynamische Speicherallokation bewusst 
verzichtet und der Speicher rein statisch vergeben.

Das wiederum macht viele Funktionen von C++ deutlich schwerer oder 
unmöglich. Du hast also kein new und delete, kein std::string, keine 
Container.

Natürlich gibt es auch Embedded-Programme die eine dynamische 
Speicherverwaltung verwenden. Aber da muss der Programmierer ganz genau 
aufpassen wann welcher Speicher wie allokiert und wieder freigegeben 
wird damit das nicht zum Problem wird. Oft werden da dann eigene 
Allokatoren verwendet, die z.B. den Speicher aus verschiedenen Pools 
holen etc. Das wirklich richtig zu machen ist aber schon die höhere 
Schule.

Man muss sich daher immer Überlegen ob die Vorteile diesen Mehraufwand 
rechtfertigen. Oft ist das nicht der Fall und man fährt mit einfachen, 
klassischen Funktionen und defines und const besser.

: Bearbeitet durch User
von Framlin (wolfgang_e34)


Lesenswert?

@Gerd E.
Vielen Dank für Deine Ausführungen. Das leuchtet tatsächlich ein.
Ok ... ich wollte mich eh schon immer mal näher mit „generischer“ 
Template-Programmierung befassen ;-)

von Framlin (wolfgang_e34)


Lesenswert?

@Mw E.
Erst mal Danke für Deine Mühe.

Das Seltsame ist, dass ich festgestellt hab, dass es funktioniert, wenn 
ich den ganzen Code mit printf zupflastere.

Nachdem ich in jede der beteiligten Funktionen ein printf für die 
Debugger-Console eingebaut hatte, lief es plötzlich durch und es kommen 
auch Werte zurück.

Gibt es einen Standardfehler, den ich gemacht haben könnte, der ein 
solches Symptom zeigt?

Ozi hab ich. Werd ich wohl mal von der Sommerterasse in die finstere 
Werkstatt wechseln müssen ;-)

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Dann klingt das so als würdeste auf bestimmte Events (die aus dem 
ReFman) nicht warten.

Dann plapperste mit dem I2C Master schon weiter bevor die vorherige 
Aktion fertig war.
Sowas mag der I2C Slave natürlich nicht.

Der Prozssor ist ja um einiges schneller als der I2C mit seinen 100kHz.
Sonen printf bremst dann ganz gut wenn er über nen 115200baud oder 
weniger UART ausgibt.

Zeichmal den Code, aber als Anhang.

von temp (Gast)


Lesenswert?

Wolfgang E. schrieb:
> Ok ... ich wollte mich eh schon immer mal näher mit „generischer“
> Template-Programmierung befassen ;-)

Eins sollte man dabei aber immer beachten. Je mehr man 
Template-Programmierung und auch inline-Funktionen benutzt, desto größer 
sind die Unterschiede einer zum Debuggen (-O0) erzeugten Version zur 
optimierten Variante. Wenn man das für schnelle IO-Operationen benutzt 
werden die Zeitunterschiede gewaltig. So kommt es dann, dass man fast 
nur noch mit wenigstens -O1 übersetzen muss, damit es wenigstens 
annähernd vergleichbar ist. Trotzdem wird das Debuggen dadurch massiv 
erschwert. Deshalb sind Makros für mich immer noch oft der bessere Weg. 
Denen ist es egal mit welchem Optimierungslevel übersetzt wird.

von Framlin (wolfgang_e34)


Angehängte Dateien:

Lesenswert?

@Mw E.
Hier der Code:

von Bernd K. (prof7bit)


Lesenswert?

temp schrieb:
> Trotzdem wird das Debuggen dadurch massiv
> erschwert. Deshalb sind Makros für mich immer noch oft der bessere Weg.

Makros können das Debuggen auch massiv erschweren. Zumindest dann wenn 
sie so verzwickt sind wie es nötig wäre um damit eine gescheite 
GPIO-Abstraktion zu bauen. Und dann vergisst Du an irgendwo ein 
unauffälliges Komma und der Compiler meckert an einer völlig anderen 
Stelle. Makros benutz ich nur noch für extrem simple Sachen die maximal 
ein Level tief gehen.

Eine sinnvolle Alternative zur Metaprogrammierung ist es C-Code mit 
externen Scripten in brauchbaren Hochsprachen (z.B. Python) zu 
generieren, da kann man dann auch kompliziertere Sachen machen die immer 
noch einfach zu warten sind und das Ergebnis ist sauberer und einfacher 
C-Code den man sich im Editor anschauen und durch den man problemlos 
durchsteppen kann.

von temp (Gast)


Lesenswert?

Bernd K. schrieb:
> Makros können das Debuggen auch massiv erschweren. Zumindest dann wenn
> sie so verzwickt sind wie es nötig wäre um damit eine gescheite
> GPIO-Abstraktion zu bauen.

Da gebe ich dir in gewisser Hinsicht recht. Es geht aber nicht darum, 
dass der Code der durch das Makro generiert wird schlecht debugbar ist. 
Wenn man z.B am Ein- und Ausgang einer Interruptroutine einen Pin setzt 
und rücksetzt um das Zeitverhalten am Oszi zu verfolgen, dann geht 
meistens an einem direkten Setzen der Register nichts vorbei. Alle 
inline-Funktionen werden bei -O0 trotzdem als Funktionen gerufen und so 
was ist an dieser Stelle tödlich.

Ein simples Makro ala GPIO_SET() oder GPIO_CLR() wird ja wohl nicht so 
verzwickt werden, außer man hat nicht alle Latten am Zaun. Die 
Initialisierung der GPIOs ist eine andere Geschichte, die bis auf wenige 
Ausnahmen niemals zeitkritisch ist. Da kann man problemlos alles 
verwenden wonach einem der Sinn steht. Wer's mag sogar die HAL.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Da bin ich wieder!

> I2C1->CR2 = 0x08; // example from RM0383 -> do not exactly know why
Ja das is auch etwas eklig, aber dazu einfach mal die Beispiele im 
RefMan selber durchrechnen und man hats einigermaßen kapiert.
Diese Zahlen müssen zum Takt des APB passen an dem der I2C hängt.
Sonst kommen vllt sogar mehr als 100kHz bei raus.

>res = (I2C1->SR2);  //dummy read to clear - see status register 2
>if (res) res = 0;   //to avoid warning
Damit er auch wirklich SR2 ausließt sollte res noch volatile sein.
Sowie kein char, das Register hat mehr als 8Bit.

Jedenfalls sehe ich jetzt nicht, dass da vergessen wurde zu warten.

Woher kommen denn die Registerdefinietionen? Von ST?
Oder selbergebaut? Dann auch mal zeigen.

Hat den das Debuggen mit dem Oszi schon was ergeben?

von Framlin (wolfgang_e34)


Lesenswert?

@Mw E.: Ich hab es mittlerweile im Oszi angeschaut und habe dabei 
gelernt, dass es einen riesen Unterschied macht, ob man das uC-Programm 
"im Debugger" laufen lässt, oder im "Standalone-Mode".

Nachdem ich das Nucleo-Board losgelöst von Rechner und Debugger hab 
laufen lassen, konnte das Oszi problemlos I2C identifizieren und die 
Kommunikation anzeigen. Davor ging das selten bis gar nicht (mit und 
ohne printf).

I2C an sich funktioniert also offenbar. Zumindest schaut es für meine 
laienhaften Augen plausibel aus und das Oszi kann etwas erkennen.

Allerdings hab ich offensichtlich noch nicht verstanden, wie man die 
drei Sensoren, die auf dem DOF-9 board von Adafruit verbaut sind, dazu 
bringt sinnvolle Informationen auszuspucken.
Im Moment findet zwar Kommunikation statt und ich bekomme teilweise auch 
Werte zurück, aber die sind konstant.
D.h. selbst wenn ich das Teil gegen die Wand werfen würde, würde es mir 
keine Bewegung melden ;)

Scheinbar bin ich momentan noch zu blöd so ne Spec richtig zu 
interpretieren .....

Die Registerdefinitionen kommen übrigens von ST, die RegisterAdressen 
für das Sensorboard hab ich von Adafruit übernommen.

Danke für den Hinweis mit dem volatile und dem char, ich hab das 
geändert, hat aber keinen Unterschied gemacht ....

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Wegen der Speicherverwaltung und C++:

Bei Arduino und dem ESP8266 ist unerwarteter Speichermangel das Problem 
Nummer 1. Steht ganz oben auf der Liste.

Daran sieht man, dass die Objekte von C++ zwar einiges vereinfachen (was 
Arduino attraktiv macht) aber andererseits schafft es neue, für Anfänger 
unerwartet schwierige Probleme.

Bei Arduino ist die String Klasse so richtig böse. Aber erzähle mal 
einem Arduino Anfänger, dass er auf diese Klasse verzichten soll. Das 
ist so, als ob du dem Koch seine Töpfe weg nehmen würdest.

C++ auf Mikrocontroller ist ein zweischneidiges Schwert. Nach mehreren 
Jahren Erfahrung bin ich immer noch unsicher, ob es für Anfänger eher 
gut oder eher schlecht ist. Ich tendiere zunehmend dazu es Anfängern 
nicht zu empfehlen.

von Framlin (wolfgang_e34)


Lesenswert?

@Stefanus F.
Kann ich in gewisser Weise nachvollziehen.
Mir geht es aber gerade darum, dass ich C++ auf uCs verwenden möchte.
Von daher nehm ich die ganzen Probleme auch "gern" in Kauf, weil sie 
eben part of the game sind, das ich spielen möchte ;)

Arduino ist mir dahingehend zu langweilig, da gibt es für jeden Mist ne 
Lib und bislang haben die immer funktioniert. Abgesehen davon find ich 
die IDE Arduino unbenutzbar, abstoßend und .....

von Framlin (wolfgang_e34)


Lesenswert?

Arduino war indes doch ganz nützlich, als ich mir in 10 min eine 
Beispielapp zusammenbasteln konnte, deren Verhalten ich dann am Oszi mit 
meiner aktuellen App vergleichen und zum Debuggen nutzen konnte,
Nun funktioniert I2C wie gewünscht und ich kann einen Servo analog zu 
den Bewegungen meines Magnetometer_Sensors steuern ;-)

Was ich allerdings noch nicht ganz verstehe sind Timer-IRQs. Da macht 
meine Implementation keinen Unterschied zwischen den Channels, d.h. es 
„feuern“ immer alle 4 channels eines Timers gkeichzeitig. Das scheint 
mir falsch zu sein ......

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.