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)?
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.
@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.
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.
@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 ....
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.
@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 .....
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)
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 ....
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:
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:
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
Servosteerig(STEERING_PORT,STEERING_PIN);
2
Compasscompass(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.
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.
@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?
@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
unsignedcharresult;
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
returnresult;
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
unsignedcharI2CPort::READ(intack){
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?
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?
@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
voidI2CPort::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
voidI2CPort::ADDR(unsignedcharaddr){
2
charres;
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
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?
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.
@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 ;-)
@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 ;-)
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.
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.
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.
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.
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?
@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 ....
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.
@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 .....
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 ......