Forum: Mikrocontroller und Digitale Elektronik Wieso ändert sich die Bitlänge?


von Moe E. (moe_espunkt)


Lesenswert?

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
void sende_frame(void) // Master station polling & refresh data
2
{
3
  #define laenge 768
4
  int i=0,m=0,l=0;
5
  
6
  int NRZI_FFF_flag_before_High  [24]     = {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1}; 
7
  int NRZI_FFF_flag_before_Low   [24]     = {1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0};
8
  int NRZI_adressdaten           [17]     = {1,1,1,1,1,0,0,0,0,0,1,0,1,0,1,0,1};
9
  int NRZI_statusdaten           [16]     = {1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0}; 
10
  int NRZI_nutzdaten             [laenge] = {1,0,1,0,1,0,1,0,...ganz viele "0" und "1"...,1,0,1,0,1,0,1,0,};
11
  int NRZI_CRC                   [16]     = {0,1,1,0,1,1,0,0,1,0,0,0,1,0,1,0}; 
12
  
13
  int NRZI_frame_senden [24+17+16+laenge+16+24]; // Alle Arrays hier "sammeln"
14
15
  for(i=0;i<24;i++)
16
  { NRZI_frame_senden[i]                    = NRZI_FFF_flag[i];    }
17
  for(i=0;i<17;i++)
18
  { NRZI_frame_senden[i+24]                 = NRZI_adressdaten[i]; }
19
  for(i=0;i<16;i++)
20
  { NRZI_frame_senden[i+(24+17)]            = NRZI_statusdaten[i]; }
21
  for(i=0;i<laenge;i++)
22
  { NRZI_frame_senden[i+(24+17+16)]         = NRZI_nutzdaten[i];   }
23
  for(i=0;i<16;i++)
24
  { NRZI_frame_senden[i+(24+17+16+laenge)]  = NRZI_CRC[i];         } 
25
  for(i=0;i<24;i++)
26
  {NRZI_frame_senden[i+(24+17+16+laenge+16)]= NRZI_FFF_flag_before_Low[i];}
27
28
  /***************************PIN HIGH/LOW SETZEN******************************************/
29
          for (m=0;m<(24+17+16+laenge+16+24);m++)
30
          {
31
            if(NRZI_frame_senden[m] == 1)
32
            {
33
                  RIN_GPIO->P5B = 0x17; // Pin des µC auf "High"
34
                  i = 62; 
35
                  while(i --)
36
                  {
37
                      __NOP();  // 30ns-40ns / NOP
38
                  }
39
            }
40
            else
41
            {
42
                  RIN_GPIO->P5B = 0x07; //Pin des µC auf "Low"
43
                  i = 88; 
44
                  while(i --)
45
                  {
46
                      __NOP();  // 30ns-40ns / NOP
47
                  }             
48
            }
49
          }             
50
  /************************************************************************/

Nun zum Problem: ändere ich nun folgende Zeile ab:
1
  for(i=0;i<24;i++)
2
  {NRZI_frame_senden[i+(24+17+16+laenge+16)]  = NRZI_FFF_flag_before_Low[i];}

und zwar auf:
1
  if(NRZI_CRC[15]==0)
2
  {
3
      for(i=0;i<24;i++)
4
      {NRZI_frame_senden[i+(24+17+16+laenge+16)]  = NRZI_FFF_flag_before_Low[i];}
5
  }
6
  else
7
  {
8
      for(i=0;i<24;i++)
9
      {NRZI_frame_senden[i+(24+17+16+laenge+16)]  = NRZI_FFF_flag_before_High[i];}
10
  }

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.

von Peter II (Gast)


Lesenswert?

vergleiche den ASM-Code - der Compiler wird einfach etwas umsortiert 
haben.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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...

von San L. (zwillingsfreunde)


Lesenswert?

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

: Bearbeitet durch User
von Stefan (Gast)


Lesenswert?

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

von Moe E. (moe_espunkt)


Lesenswert?

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?

von Lutz H. (luhe)


Lesenswert?

Auf dem seriellen Bus könnten irgendwelche Bitmuster Steuerungssignale,
z.B. Start, Stop, Pause bedeuten. Wenn so etwas auftritt, reagiert der 
Bus entsprechen.

von San L. (zwillingsfreunde)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von TriHexagon (Gast)


Lesenswert?

1
int i=0,m=0,l=0;
2
  
3
int NRZI_FFF_flag_before_High  [24]     = {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1}; 
4
int NRZI_FFF_flag_before_Low   [24]     = {1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0};
5
int NRZI_adressdaten           [17]     = {1,1,1,1,1,0,0,0,0,0,1,0,1,0,1,0,1};
6
int NRZI_statusdaten           [16]     = {1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0}; 
7
int NRZI_nutzdaten             [laenge] = {1,0,1,0,1,0,1,0,...ganz viele "0" und "1"...,1,0,1,0,1,0,1,0,};
8
int NRZI_CRC                   [16]     = {0,1,1,0,1,1,0,0,1,0,0,0,1,0,1,0}; 
9
  
10
int NRZI_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.

von (prx) A. K. (prx)


Lesenswert?

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)".

von Konrad S. (maybee)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

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.

von Stefan (Gast)


Lesenswert?

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

von Thosch (Gast)


Lesenswert?

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.

von TriHexagon (Gast)


Lesenswert?

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 :).

von Peter D. (peda)


Lesenswert?

Thosch schrieb:
> Da wäre ein int also sogar 32 Bit breit und es werden pro Byte 3 Byte
> verschwendet.

Nö, es werden pro Bit 31 Bits verschwendet.

von Lutz H. (luhe)


Lesenswert?

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.

von Georg (Gast)


Lesenswert?

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

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.