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)
:
Bearbeitet durch User
@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 ;-)
Also bei sowas bekomm ich nen Schreikrampf:
1 | RCC->APB1ENR |= 1 <<21; |
2 | while (!(I2C1->SR1 & 0x0002)); |
Die Bits haben Namen...
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
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:
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 | }
|
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 | } |
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
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
@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 | 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
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 | 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
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.
:
Bearbeitet durch User
@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 ....
:
Bearbeitet durch User
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 ......
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.