Hallo, ich möchte auf einem F103 Blue Pill Board einen HCT595 mit Bits befüllen. Mangels freier SPI schiebe ich die Bits einzeln heraus. Frage: Muss man bei eingeschalteter GCC Optimierung -Os beim Toggeln eines Portpins einen Delay einlegen? Ich kenne die interne Schaltung des Cortex nicht, weiss nicht wie Piplines oder Caching da reinfummeln können, denn das physikalische Setzen eines Pins mit sofortigem Rücksetzen könnte ja auch verschluckt werden. Könnte mir da jemand eine sichere Lösung nennen, die aber optimale Geschwindigkeit bietet? (72 Mhz) Grüße, Christian
Überrennen wirst du nichts, keine Sorge. Erwarte aber keine herausragende Toogle-Frequenz vom F103, denn bei dem hängen die GPIOs an einem der APB Peripheriebusse. Bei späteren STM32 hängen sie am flotteren AHB.
Christian J. schrieb: > Frage: Muss man bei eingeschalteter GCC Optimierung -Os beim Toggeln > eines Portpins einen Delay einlegen? [...] 72 MHz Ja, aber nur weil der Pin sonst nicht hinterher kommt. Ich würde einfach ein oder zwei __NOP(); einfügen - und mit dem Oszi das Signal kontrollieren, falls was nicht tut. Verschluckt wird nix, aber der Pin hat ja nur eine begrenzte Stromstärke und muss die Eingagskapazität des/der 595 umladen. Übrigens kommt es auch darauf an wie genau Du die Ports setzt - falls da ein Funktionsaufruf statt findet, brauchst Du keine Nops mehr einfügen. Direktes Schreiben der Pinsetz/Pinlösch- Register geht am schnellsten.
Jim M. schrieb: > Verschluckt wird nix, aber der Pin hat ja nur eine begrenzte Stromstärke > und muss die Eingagskapazität des/der 595 umladen. So krass ist die Eingangskapazität eines nah angebundenen 595 nicht, dass man sich eigens deshalb Sorgen machen müsste. Die GPIO Pins sind zudem auf die vorgesehene Frequenz konfigurierbar.
:
Bearbeitet durch User
code von elm chan für AVR, geht auch beim ARM
1 | static
|
2 | void xmit_mmc ( |
3 | const BYTE* buff, /* Data to be sent */ |
4 | UINT bc /* Number of bytes to send */ |
5 | )
|
6 | {
|
7 | BYTE d; |
8 | |
9 | |
10 | do { |
11 | d = *buff++; /* Get a byte to be sent */ |
12 | if (d & 0x80) DI_H(); else DI_L(); /* bit7 */ |
13 | CK_H(); CK_L(); |
14 | if (d & 0x40) DI_H(); else DI_L(); /* bit6 */ |
15 | CK_H(); CK_L(); |
16 | if (d & 0x20) DI_H(); else DI_L(); /* bit5 */ |
17 | CK_H(); CK_L(); |
18 | if (d & 0x10) DI_H(); else DI_L(); /* bit4 */ |
19 | CK_H(); CK_L(); |
20 | if (d & 0x08) DI_H(); else DI_L(); /* bit3 */ |
21 | CK_H(); CK_L(); |
22 | if (d & 0x04) DI_H(); else DI_L(); /* bit2 */ |
23 | CK_H(); CK_L(); |
24 | if (d & 0x02) DI_H(); else DI_L(); /* bit1 */ |
25 | CK_H(); CK_L(); |
26 | if (d & 0x01) DI_H(); else DI_L(); /* bit0 */ |
27 | CK_H(); CK_L(); |
28 | } while (--bc); |
29 | }
|
Hallo, ok. Liege ich hiermit jetzt total falsch? Ein AVR hat mit einem ARM allerdings gemeinsam und solche Sachen wie oben sind mir zu aufgeblasen. Man kann auch Schleifen entrollen lassen wenn man will. Und so 10 Mhz reichen auch, das menschliche Auge kommt da eh nicht mehr mit bei den LEDs, die am 595 dran sind.
1 | /* Den 74HCT595 konfigurieren */
|
2 | GPIO_StructInit(&GPIO_InitStructure); // Struct Init |
3 | GPIO_InitStructure.GPIO_Pin = HC595_DS | HC595_STCP | HC595_OE | HC595_SHCP; |
4 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; |
5 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; |
6 | GPIO_Init(HC595_PORT, &GPIO_InitStructure); // Ausführen |
7 | |
8 | GPIO_SetBits(HC595_PORT,HC595_OE); // Outputs = Z-State |
9 | GPIO_ResetBits(HC595_PORT,HC595_STCP); // Storage Register = LOW |
10 | GPIO_ResetBits(HC595_PORT,HC595_SHCP); // Shift Register = LOW |
11 | |
12 | /* Schreibt ein 8 Bit Wort in den 74HCT595 */
|
13 | void Set74HCT595(uint8_t data) |
14 | {
|
15 | for (uint8_t i = 0; i < 8; i++) { |
16 | // Datenbit setzen
|
17 | GPIO_WriteBit(HC595_PORT,HC595_DS,(data & 0x80)); __DMB(); // Bit anlegen |
18 | GPIO_SetBits(HC595_PORT,HC595_SHCP); __DMB(); // SHCP -> High |
19 | GPIO_ResetBits(HC595_PORT,HC595_SHCP); __DMB(); // SHCP -> LOW |
20 | data = data << 1; |
21 | }
|
22 | // 16 Bit an die Ausgaenge clocken
|
23 | GPIO_SetBits(HC595_PORT,HC595_STCP); __DMB(); // STCP -> HIGH |
24 | GPIO_ResetBits(HC595_PORT,HC595_STCP); __DMB(); // STCP -> LOW |
25 | GPIO_ResetBits(HC595_PORT,HC595_OE); __DMB(); // OE -> LOW (aktiv) |
26 | }
|
Christian J. schrieb: > Hallo, > > ich möchte auf einem F103 Blue Pill Board einen HCT595 mit Bits > befüllen. Mangels freier SPI schiebe ich die Bits einzeln heraus. Du kannst auch mehrere ICs an den SPI hängen.
Christian J. schrieb: > /* Schreibt ein 8 Bit Wort in den 74HCT595 */ > void Set74HCT595(uint8_t data) > { > for (uint8_t i = 0; i < 8; i++) { > // Datenbit setzen > GPIO_WriteBit(HC595_PORT,HC595_DS,(data & 0x80)); __DMB(); // > Bit anlegen > GPIO_SetBits(HC595_PORT,HC595_SHCP); __DMB(); // > SHCP -> High > GPIO_ResetBits(HC595_PORT,HC595_SHCP); __DMB(); // > SHCP -> LOW > data = data << 1; Mir kommen da Bedenken bzgl. Beleidigung der Ostfriesen, wenn ich bei obigem an die Frage denke, wie viele Ostfriesen man zum Reinschrauben einer Glühbirne braucht. Geht's vielleicht noch etwas komplizierter? W.S.
Christian J. schrieb: > for (uint8_t i = 0; i < 8; i++) Genereller Tip für AVR-Umsteiger: nimm auf ARM Cortex-M keine 8bit-Variablen, wenn dies nicht unumgänglich ist (Strings, große Structs, Grafiken oder so). Es ist deutlich langsamer, als wenn man mit 32bit arbeitet. Insbesondere Schleifenvariablen sollten immer mit 32bit realisiert werden. Entweder, Du nimmst gleich uint32_t, oder wenn Du es richtig sauber machen willst, dann lautet der korrekte Datentyp "uint_fast8_t".
Nop schrieb: > Entweder, Du nimmst gleich uint32_t, oder wenn Du es richtig sauber > machen willst, dann lautet der korrekte Datentyp "uint_fast8_t". Richtig sauber? Quatsch mit Soße sowas. Für normale lokale Variablen einfach int oder long und gut isses. Die angebrannte Diskussion über uintXYZ_t hatten wir schon - und sie war komplett albern. Nein, das Codebeispiel von Christian sieht einfach grauselig aus. Das ist der Punkt.
1 | long m; |
2 | |
3 | m = 0x80; |
4 | while (m) |
5 | { clockbit_low(); |
6 | databit(data & m); |
7 | clockbit_high(); |
8 | m = m>>1; |
9 | }
|
und die Funktionen void clockbit_low(void) und void clockbit_high(void) sowie void databit(bool b) kann sich der TO selber schreiben. Das läuft im Prinzip auf ein __inline void .... mit GPIOx_BSRR = 1<<bitnummer oder 1<<(bitnummer+16) heraus. W.S.
Christian J. schrieb: > Könnte mir da jemand eine sichere Lösung nennen, die aber optimale > Geschwindigkeit bietet? (72 Mhz) Beim STM kannst Du per DMA Daten zwischen SRAM und Peripherie verschieben. Du kannst also mit ein paar GPIO's und DMA eine flotte SPI emulieren. Für die DMA brauchst Du noch einen Trigger, aber irgendein Timer wird sich noch finden, oder? Das ist typisch für einen ARM + IP-Cores -> Hohe mittlere Rechenleistung in der CPU, aber richtig schnell wird das Embedded Dingens erst mit DMA. Und wenn der -o3 GCC zu langsam ist, bleibt immer noch die Möglichkeit Assembler zu schreiben. ;) Grad nochmal geschaut - einem NXP HCT595 @ 3V3 bei Zimmertemperatur würde ich nicht mehr als 10..15MHz zumuten. ;)
W.S. schrieb: > Richtig sauber? Quatsch mit Soße sowas. Es stand im zitierten Text deutlich "uint8_t"; anders als Dir sind dem Mitposter also die Errungenschaften von C99 vertraut. Nur falsch benutzt für ARM. > Für normale lokale Variablen einfach int oder long und gut isses. Und dann rumheulen, daß die Datentypen überall ne andere Breite haben. Nimm einfach mal zur Kenntnis, daß der Rest der C-Welt die portablen Datentypen seit 18 Jahren verstanden hat UND in der Lage ist, damit den eigenen Code leserlicher zu gestalten.
Nop schrieb: > Nimm einfach mal zur Kenntnis, daß Nimm du einfach mal zur Kenntnis, daß wir da ganz offensichtlich völlig konträre Positionen vertreten UND daß die Welt weitaus größer ist als nur die C99-Welt UND daß ich keine Lust mehr habe, mit all den uint123_t Leuten darüber zu diskutieren. Sowas wie "uint_fast8_t" ist die Krätze und auf keinem ARM vonnöten. Also laß es, wir sollten uns besser um ganz andere Probleme kümmern. W.S.
Nop schrieb: > Es ist deutlich langsamer, als wenn man mit > 32bit arbeitet. Insbesondere Schleifenvariablen sollten immer mit 32bit > realisiert werden. Stimmt das wirklich? Denn eine 8 Bit Variable braucht auch nur 8 Bit im Speicher und habe mal gelernt, dass man immer den kleinsten Datentyp verwenden soll der geht. Und bei den stdtype.h Typen weiss ich nunmal wie lang die sind, bei einem int nicht. Ok, der hat hier 32 Bit aber woanders eben nicht. Wie managed der Arm denn Variablen, die kleiner als die Busbreite sind? PS: Danke für den TIP; dass eine SPI auch mehrere Geräte ueber den CS steuern kann. Völlig übersehen :-) @W.S.: Ich magh Deine schnoddrige Art nicht aber das lass ich mal aussen vor. Ich benutze soweit es geht die mitgelieferten Funktionen, wobei es völlig unerheblich ist wieviel Quelltext die produzieren. Kompiliert und Optimiert ist die Schleife winzig klein, da der Compiler die Subroutinen alle wegoptimiert und nur noch registerzugriffe einfügt. Totoptimiert lässt man sie von 8 runter auf 0 laufen und spart noch ein Compare ein, hat nur noch einen Zero Flag Test. DMA ist mir zwar gut vertraut aber hier eher nicht angebracht. Es werden 5 kaskadierte HC595, die viele LEDs dran haben gefüttert und das nur recht selten. Ist eine Bargraph Anzeige für Spannungen. Und um die 5x8 Bit raus zu trommeln habe ich die Zeit. DMA ist auch recht viel nachdenken, mache ich gerne bei AD Wandlern, die mir fortlaufend Arrays vollschreiben, die ich dann gleich auslesen kann. Mehr als 8 Mhz habe ich auf Lochrasterplatine noch nicht gescheit zum Laufen gekriegt. Ab 12 Mhz verhaspeln sich die Bits, zb bei einem Display.
Christian J. schrieb: > Wie managed der Arm denn Variablen, die kleiner als die Busbreite sind? Nicht nur bei ARM, auch bei x86 sind Typen kleiner als "int" im Nachteil.
1 | extern int g1(unsigned char); |
2 | extern int g2(unsigned); |
3 | |
4 | void f1(unsigned char a, unsigned char b, unsigned char c) |
5 | {
|
6 | if (a+b > c) |
7 | g1(a+b); |
8 | }
|
9 | |
10 | void f2(unsigned a, unsigned b, unsigned c) |
11 | {
|
12 | if (a+b > c) |
13 | g2(a+b); |
14 | }
|
ARM, gcc 4.9.2, -O2:
1 | f1: add r0, r0, r1 |
2 | cmp r0, r2 |
3 | bgt .L4 |
4 | bx lr |
5 | .L4: uxtb r0, r0 ;<--- truncation nötig |
6 | b g1 |
7 | |
8 | f2: add r0, r0, r1 |
9 | cmp r0, r2 |
10 | bhi .L7 |
11 | bx lr |
12 | .L7: b g2 |
x86-64, gcc 4.7.2, -O2, besonders krass, da der Compiler zwar einen korrekt auf 32-Bits erweiterten Wert an g1() übergibt, aber bei den eigenen Parametern von 8-Bit Werten ausgeht und folglich erst einmal erweitert:
1 | f1: movzbl %dil, %ecx |
2 | movzbl %sil, %eax |
3 | movzbl %dl, %edx |
4 | addl %ecx, %eax |
5 | cmpl %edx, %eax |
6 | jg .L4 |
7 | rep |
8 | ret |
9 | .L4: addl %esi, %edi |
10 | movzbl %dil, %edi |
11 | jmp g1 |
12 | |
13 | f2: addl %esi, %edi |
14 | cmpl %edx, %edi |
15 | ja .L7 |
16 | rep |
17 | ret |
18 | .L7: jmp g2 |
:
Bearbeitet durch User
Um Bitbanging direkt zu machen, bieten sich die BRR und BSRR Register an:
1 | // from VL Discovery
|
2 | #define BLUE_LED_PIN GPIO_Pin_8
|
3 | #define GREEN_LED_PIN GPIO_Pin_9
|
4 | #define LED_GPIO_PORT GPIOC
|
5 | |
6 | // light the blue LED
|
7 | LED_GPIO_PORT->BSRR = BLUE_LED_PIN; |
8 | // clear the blue LED
|
9 | LED_GPIO_PORT->BRR = BLUE_LED_PIN; |
10 | // light the blue and green LED
|
11 | LED_GPIO_PORT->BSRR = BLUE_LED_PIN | GREEN_LED_PIN; |
12 | // and switch them off
|
13 | LED_GPIO_PORT->BRR = BLUE_LED_PIN | GREEN_LED_PIN; |
Pins sollten bereits als Ausgang definiert sein. Siehe RM0008, Seite 168 und 169.
:
Bearbeitet durch User
Nop schrieb: > Es ist deutlich langsamer, als wenn man mit > 32bit arbeitet. Insbesondere Schleifenvariablen sollten immer mit 32bit > realisiert werden. Was ist für Dich deutlich, 1%? Ich würde die Compilerbauer nicht für Idioten halten, die werden als Loopzähler natürlich ein ganzes Register nehmen, d.h. keinerlei merkbaren Geschwindigkeitseinbuße. Und selbst wenn in einer kleinen Loop von 20 Befehlen noch ein überzähliges AND 0x000000FF drin sein sollte, merkt das niemand. Sowas zählt für mich unter Mikrooptimierung, da ist mir die Portabilität deutlich mehr wert. Ich bin jetzt auch dabei, in printf/scanf die portablen Formatbezeichner zu verwenden.
Peter D. schrieb: > Nop schrieb: >> Es ist deutlich langsamer, als wenn man mit >> 32bit arbeitet. Insbesondere Schleifenvariablen sollten immer mit 32bit >> realisiert werden. . > Was ist für Dich deutlich, 1%? > Ich würde die Compilerbauer nicht für Idioten halten, die werden als > Loopzähler natürlich ein ganzes Register nehmen, d.h. keinerlei > merkbaren Geschwindigkeitseinbuße. > Und selbst wenn in einer kleinen Loop von 20 Befehlen noch ein > überzähliges AND 0x000000FF drin sein sollte, merkt das niemand. . > Sowas zählt für mich unter Mikrooptimierung, da ist mir die Portabilität > deutlich mehr wert. > Ich bin jetzt auch dabei, in printf/scanf die portablen Formatbezeichner > zu verwenden. Genau, weil sie eh "ein ganzes Register nehmen", macht es keinen Sinn da ein paar Bits sparen zu wollen. Aber ich werde mal den GCC fragen, wie er das sieht.
:
Bearbeitet durch User
Christian J. schrieb: > @W.S.: Ich magh Deine schnoddrige Art nicht aber das lass ich mal aussen > vor. Ich benutze soweit es geht die mitgelieferten Funktionen, wobei es > völlig unerheblich ist wieviel Quelltext die produzieren. Nun, da mußt du halt mit zurechtkommen, schließlich muß ich ja auch mit mir selbst zurechtkommen - ABER: Wieso Nop auf die Idee gekommen ist, ausgerechnet so ein Ungetüm wie "uint_fast8_t" vorzuschlagen, ist mir ein Rätsel. Er hat doch ansonsten recht vernünftige Ansichten. Guck dir mal meinen aus dem Handgelenk geschlenkerten Vorschlag an, da wirst du sehen, daß es auch ganz ohne Schleifenzähler geht und damit die ganze Diskussion um "uint_fast8_t" und Konsorten per se völlig überflüssig ist. Sieht denn sowas Simples kein anderer unter den Disputanten hier? Ich sag's mal so: Ein guter Programmierer zeichnet sich nicht dadurch aus, daß er sämtliche "uint_fast8_t" und Konsorten auswendig kennt, sondern daß er gute Herangehensweisen und gute Algorithmen draufhat. Es ist wichtig, den Kern einer Sache schnell und gründlich zu durchblicken. Das ist nicht nur Erfahrungssache, sondern braucht auch Kenntnisse von Dingen, die scheinbar außerhalb des Gesichtskreises liegen. Olle Napoleon soll wohl mal Bewerber auf einen Offiziers-Posten gefragt haben "Hat er Fortune?" - nee, nicht ob er bislang Schwein gehabt hat.., eher ob er Durchblick und ein Händchen für's Richtig-Machen und damit für's Siegen hat. Er brauchte eher nen Strategen und keinen Beckmesser. Das ist der Punkt. Angehenden C-Programmierern würde ich deswegen anraten, parallel dazu ein Mathe-Studium zu belegen, Elektrotechnikern und Informatikern ein paralleles Physikstudium und Chemikern ein Stück Maschinenbau. Kurzum, einen größeren eigenen Horizont zu erwerben. W.S.
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.