Hallo Zusammen,
ich habe ein kurioses Problem, was ich mir nicht erklären kann.
Ich habe ein Array mit 865 Feldern, die durch Bits (also ein Feld ist
entweder "0" oder "1") belegt sind. Nun möchte ich diese Bits seriell
auf einem RS485 Bus ausgeben, in dem ich jedes Feld abfrage und den
jeweiligen Pin des µC auf "High" bzw. auf "Low" setze.
Ein Bit soll eine Länge von 6,4µs haben: Wenn der Pin des µC auf "High"
bzw. auf "Low" gesetzt wird, dann soll dieser Zustand 6,4µs anhalten.
Das realisiere ich durch NOPs (no operation, siehe auch Quellcode).
Das funktioniert auch problemlos: auf dem Oszilloskop messe ich eine
Bittlänge von 6,36µs(High) und 6,5µs(Low), was ausreichend ist.
Der Quellcode dafür sieht wie folgt aus:
1
voidsende_frame(void)// Master station polling & refresh data
Dann messe auf dem Oszilloskop plötzlich für ein "High" eine Bitlänge
von 5,18µs und für "Low" 7,34 µs! Dabei wurden die NOPs (und die
dazugehörige Schleife) nicht geändert! Wie kann das sein?
Vor der Abänderung habe ich für "High" eine Bitlänge von 6,36µs und für
"Low" 6,5µs gemessen.
Hat jemand eine Vermutung woran es liegen könnte ? Oder eine Idee wie
man die Realisierung der Zeiten besser programmieren könnte?
Ich verwende als µC den R-IN32M3-CL (enthält ARM Cortex M3) .
Ich bedanke mich für eure Hilfe und hoffe, dass ich das Problem
ausreichen erläutert habe.
Moe Espunkt schrieb:> Oder eine Idee wie man die Realisierung der Zeiten besser programmieren> könnte?
Man könnte die Bits über eine Hardwareschnittstelle versenden. Ein UART
oder ein SPI ist da sehr geeignet.
Diese NOP-Geschichte ist auf jeden Fall Murks: wenn du den Code mit
einer neuen Compilerversion übersetzt läuft es wieder nicht, oder sogar
besser. Auf jeden Fall anders...
Peter II schrieb:> vergleiche den ASM-Code - der Compiler wird einfach etwas umsortiert> haben.
Vermutlich.
Kannst du ASM? Falls ja, schreib die paar Zeilen in ASM, dann dürfte es
kein Problem sein die gewünschte Zeit zu erreichen.
Wieso eigentlich keine Hardware Schnittstelle benutzt?
Edit: Da kam wohl einer eher auf den gedanken :P
Mit den nops wirst Du nie auf Dauer glücklich werden. Nur eine kleine
Änderung am Compiler oder am System wird Dir alles über den Haufen
werfen:
* andere Compiler-Optimierung
* anderes CPU-Timing
* anderes RAM-Timing
* Interrupts während Deiner Code-Ausführung
* DMA-Zugriffe während Deiner Code-Ausführung
Folgende Lösungen bieten sich wesentlich besser an (von gut nach
schlecht):
* verwende einen Hardware-UART
* verwende eine Hardware-SPI
* verwende GPIO-Ansteuerung über DMA über Timer
* verwende GPIO-Ansteuerung über Timer-Interrupts
Gruß, Stefan
Vielen Dank für eure Antworten !
Lothar Miller schrieb:> Man könnte die Bits über eine Hardwareschnittstelle versenden. Ein UART> oder ein SPI ist da sehr geeignet.
UART und SPI waren auch meine ersten Ideen gewesen, allerdings zwingen
mich die Umstände dazu, die Übertragung "von Fuß" aus zu machen: Die
Gegenstelle, der ich die Bits sende, arbeitet asynchron und Frame
synchonisiert (HDLC-Format). UART eignet sich nicht , wegen des Start-
und Stopbits und SPI arbeitet synchron. Ergo, ich sende die Bits
selbstständig
San Lue schrieb:> Kannst du ASM? Falls ja, schreib die paar Zeilen in ASM, dann dürfte es> kein Problem sein die gewünschte Zeit zu erreichen.
ASM-Kenntnisse sind kaum welche da: Ich hab' mit ASM ca. 2-3 mal im
Rahmen einer Schulveranstaltung etwas gemacht, aber meine Kenntnisse
reichen bei weitem nicht aus. Ist es möglich sich ASM in "kurzer Zeit"
anzueignen?
Auf dem seriellen Bus könnten irgendwelche Bitmuster Steuerungssignale,
z.B. Start, Stop, Pause bedeuten. Wenn so etwas auftritt, reagiert der
Bus entsprechen.
Moe Espunkt schrieb:> ASM-Kenntnisse sind kaum welche da: Ich hab' mit ASM ca. 2-3 mal im> Rahmen einer Schulveranstaltung etwas gemacht, aber meine Kenntnisse> reichen bei weitem nicht aus. Ist es möglich sich ASM in "kurzer Zeit"> anzueignen?
Ist nicht sonderlich kompliziert. Wer ein bisschen versteht wie ein uC
funktioniert dürfte nicht extreme Probleme mit ASM haben. Ist aber alles
eine Frage der definition was "kurze Zeit" für dich bedeutet.
Wenn das System irgendwo mit Interrupts arbeitet, dann wird es
schwierig, ein solches Timing exakt einzuhalten.
Für Tipps wäre es möglicherweise sinnvoll, etwas mehr über das
asynchrone Schema zu verraten. Denn HDLC asynchron ist schon etwas
exotisch. Also beispielsweise, was genau den Sender daran hindert, die
Bits im präzisen Takt zu senden.
intNRZI_frame_senden[24+17+16+laenge+16+24];// Alle Arrays hier "sammeln"
Eine kleine Anmerkung, das hilft dir im Moment zwar nicht weiter aber
dafür später mal. Bei dem Code nehme ich mal an, dass du bewusst die
Bits nicht in Bytes drückst, ansonsten verschwendest du ziemlich viel
Speicher (in diesem Fall auch noch RAM). int ist auf AVR 2 Bytes groß
weshalb der Speicherverbrauch doppelt so groß ist als nötig. Entweder du
kompilierst mit -mint8 (nicht zu empfehlen), nutzt unsigned char oder
gleich die Datentypen aus stdint.h (zu empfehlen), welche von avr/io.h
automatisch inkludiert werden. Dann nutzt du einfach uint8_t -> 1 Byte
lang.
TriHexagon schrieb:> Speicher (in diesem Fall auch noch RAM). int ist auf AVR 2 Bytes groß> weshalb der Speicherverbrauch doppelt so groß ist als nötig.
Eher mehr, weil "R-IN32M3-CL (enthält ARM Cortex M3)".
Moe Espunkt schrieb:> und SPI arbeitet synchron
... zum SPI-Clock-Signal, das du mit passendem Timing generieren kannst.
Die Frage ist nur, ob deine SPI-Hardware aufeinanderfolgende Bytes ohne
"Stottern" senden kann. Manche SPI-Hardware legt zwischen Bytes eine
Pause von einem Taktzyklus ein.
A. K. schrieb:> TriHexagon schrieb:>> Speicher (in diesem Fall auch noch RAM). int ist auf AVR 2 Bytes groß>> weshalb der Speicherverbrauch doppelt so groß ist als nötig.>> Eher mehr, weil "R-IN32M3-CL (enthält ARM Cortex M3)".
[egentlich mehr an den TO gerichtet]
Der ganze Ansatz mit einem Array, in dem die einzelnen Elemente Bits mit
den Werten 0 bzw. 1 sind, ist doch im Grunde schon unsinnig. Das kann
man auf einem PC machen, wenn man genug Giga-Bytes zur Verfügung hat und
nur schlecht programmieren kann, aber auf einem kleineren System, auf
dem man nicht willkürlich mit dem Speicher nur so um sich schmeissen
kann, macht man das anders: man nimmt zb ein Byte her und benutzt dort
alle Bits. Anstelle von 865 Bytes zur Speicherung der Information
benötigt man dann nur noch 109.
Dazu muss man natürlich mit Bitoperationen umgehen können. Aber das ist
sowieso Grundvoraussetzung, wenn man auf dieser Ebene programmiert.
Genau so wie die Umkopieraktion der 'Bits' in ein gemeinsames Array.
Davon wird das Timing aich nicht genauer, wenn man zuerst mal alles in
einem Array zusammenpfercht. Da kann man genausogut während der Ausgabe
nacheinander auf die einzelnen Arrays umschalten ohne vorher alles
sammeln zu müssen.
Moe Espunkt schrieb:> Dabei wurden die NOPs (und die> dazugehörige Schleife) nicht geändert! Wie kann das sein?
Weil NOPs in C-Programmen ganz großer Mist sind, sobald Zeiten stimmen
sollen. Jede Code-Zeile benötigt Ausführungszeit!
Nimm wenigstens einen Compare-Output, der setzt einen Pin zyklusgenau
und unabhängig von der Programmlaufzeit, solange das Bit bereitstellen
nicht länger dauert.
Auch auf nem 32Bit Boliden muß man SRAM nicht mit Gewalt verschwenden.
Ein int kann 32 Bits speichern, nicht nur 1 Bit.
Dazu wurden extra die Schiebebefehle erfunden, um Bits nacheinander zu
testen.
Damit kann diese Aufgabe sogar ein 8Bit ATtiny45 mit 256 Byte SRAM
bequem ausführen.
Asynchron heisst doch nur, daß der Empfänger zu keiner clk-Leitung
synchron die Datenbits empfängt, sondern seinen eigenen clk aus dem
Baudratenteiler erhält. Insofern kannst Du schon SPI benutzen, indem Du
die SPI-Frequenz entsprechend Deiner Baudrate einstellst, die Sclk
wegläasst und nur MOSI zum Empfänger weitergibst. das ist dann auch
asynchron ...
Zur Lösung mit DMA:
Ich weiss nicht, wie der Renesas chip so tickt, aber bei ST gibt es ein
Register, das sowohl eine 16-Bit-Maske als auch ein 16-Bit-Pegelregister
enthält. Damit ist es möglich, mit einem Registerzugriff nur einen Pin
eines Ports high- oder low zu schalten. Das lässt sich prima für DMA
nutzen:
Eine Tabelle mit den Registerwerten für jedes "UART-Datenbit" anlegen
und diese Tabelle per timergesteuertem DMA (Timer == Baudrate) ausgeben.
Damit kannst Du beliebige Bitfolgen beliebiger Länge in einem sehr
exakten Timing ausgeben.
Gruß, Stefan
TriHexagon schrieb:> Bei dem Code nehme ich mal an, dass du bewusst die> Bits nicht in Bytes drückst, ansonsten verschwendest du ziemlich viel> Speicher (in diesem Fall auch noch RAM). int ist auf AVR 2 Bytes groß> weshalb der Speicherverbrauch doppelt so groß ist als nötig.Moe Espunkt schrieb:> Ich verwende als µC den R-IN32M3-CL (enthält ARM Cortex M3) .
Der TO benutzt keinen AVR, sondern einen 32-Bitter.
Da wäre ein int also sogar 32 Bit breit und es werden pro Byte 3 Byte
verschwendet.
A. K. schrieb:> TriHexagon schrieb:>> Speicher (in diesem Fall auch noch RAM). int ist auf AVR 2 Bytes groß>> weshalb der Speicherverbrauch doppelt so groß ist als nötig.>> Eher mehr, weil "R-IN32M3-CL (enthält ARM Cortex M3)".
Ups O.o. Naja trifft größtenteils auch auf den ARM zu, auch wenn er sich
dort weniger Sorgen um den Speicherplatz machen muss :).
Peter Dannegger schrieb:> Nö, es werden pro Bit 31 Bits verschwendet.
Die Bit kann man bei den 32 bittern oft in einen Speicherbereich legen
wo 32 Bit ein Bit groß sind.
Peter Dannegger schrieb:> Weil NOPs in C-Programmen ganz großer Mist sind, sobald Zeiten stimmen> sollen.
Ich würde noch ergänzen, für moderne Prozessoren würde ich mich auch in
Assembler nicht mehr darauf verlassen: bei einer Instruction Pipeline
kann man garnicht mehr genau angeben, wieviel Takte ein Befehl dauert,
und es kann passieren, dass schon der Prozessor selbst einen NOP einfach
weglässt. Ob das beim ARM so ist weiss ich nicht, aber immerhin ist es
ja ein 32bitter und sehr viel komplexer als ein Z80.
Georg