Forum: Mikrocontroller und Digitale Elektronik _delayus funktioniert nicht wie gewünscht


von Lars (Gast)


Lesenswert?

Hey Leute vielleicht kann mir mal jemand das folgende Verhalten 
erklären.

Ich betreibe einige LEDs im Multiplexbetrieb und habe dabei das Problem, 
dass ich die betreffenden LEDs nur immer für 100 µs aufleuchten lassen 
möchte. Dies funktioniert leider nicht wie gewünscht. Laut Oszilloskop 
sind es anstatt den eingestellten 100 µs um die 200 µs. Anbei ein Auszug 
aus der gekürzten mux_display Funktion.

Testweise habe ichh noch eine "SIGNAL_ON_LED" eingebaut. Wenn ich an 
dieser mit dem Osziloskop messe, stimmen die eingestellten 100 µs 
Einschaltdauer.
1
#define time_on 100
2
#define time_off 300
3
4
5
void mux_display()
6
{
7
8
  PORTB &= ~(1<<DIGIT2);
9
  SevenSegment(minute/10);
10
  _delay_us(time_off);
11
  PORTB |= (1<<DIGIT1);
12
  PORTC |= (1<<SIGNAL_ON_LED);
13
  _delay_us(time_on);
14
15
16
  
17
  PORTB &= ~(1<<DIGIT1);
18
  SevenSegment(minute%10);
19
  PORTC &= ~(1<<SIGNAL_ON_LED);
20
  _delay_us(time_off);
21
  PORTB |= (1<<DIGIT2);
22
  _delay_us(time_on);
23
}

von Falk B. (falk)


Lesenswert?

@ Lars (Gast)

>möchte. Dies funktioniert leider nicht wie gewünscht. Laut Oszilloskop
>sind es anstatt den eingestellten 100 µs um die 200 µs. Anbei ein Auszug
>aus der gekürzten mux_display Funktion.

Dann ist dein define für F_CPU falsch und stimmt nicht mit dem realen 
CPU-Takt überein. Oder du compilierst ohne Optimierung, dann passen die 
Zeiten auch nicht.

von Peter D. (peda)


Lesenswert?

Lars schrieb:
> Laut Oszilloskop
> sind es anstatt den eingestellten 100 µs um die 200 µs.

Das ist verständlich. Jede Codezeile kostet zusätzliche Zeit und 
besonders die Berechnungen / und %.
Deshalb multiplext niemand bei Verstand mit Delay, sondern immer mit 
einem Timerinterrupt. Dann sind die Zeiten unabhängig von 
Ausführungszeiten (kein Flackern) und der MC kann noch andere Sachen 
machen.

Die Berechnungen zieht man natürlich komplett aus dem Multiplexen raus, 
denn kein Mensch kann so schnell Änderungen ablesen. Für ergonomisches 
Ablesen verlangsamt man die Rate der Neuberechnung auf 2..5 Werte/s, 
damit der Benutzer nicht nur flackernde 8-en sieht.

von kyrk.5 (Gast)


Lesenswert?

Wie lange dauert eine Instruktion bei dir? 1us? 100ns? 10ns?

Bei 1us Instruktionzeit würde 100us delay 100 instruktionen kosten. Da 
kann man schon relatiev viel machen. Aber wenn nix anderes gemacht wird 
dann könnte man noch blockierend den _delay_us verwenden. Bei schnellere 
instruktionenzeit würde ich den _delay_us vergessen und sofort auf 
interrupt gehen.

Faustregel: alles was delay_ms oder delay_us heisst muss aus der C Code 
raus!

von Lars (Gast)


Lesenswert?

OK, nur findet zwischen Einschalten von DIGIT1 und Abschalten von DIGIT1 
keine Berechnung statt.

Einleuchtend wäre es hingegen zwischen dem Einschalten SIGNAL_ON_LED und 
Abschalten von SIGNAL_ON_LED, denn dort wird SevenSegment(minute%10) 
ausgeführt. Allerdings passen gerade hier die 100 µs.

von Falk B. (falk)


Lesenswert?

@ kyrk.5 (Gast)

>Faustregel: alles was delay_ms oder delay_us heisst muss aus der C Code
>raus!

Unsinn^3. Gerade _delay_us() ist für sehr kurze Zeiten optimal, weil in 
der Zeit so oder so nix anderes sinnvoll gemacht werden kann. Und auch 
ein _delay_ms() ist, RICHTIG eingesetzt, überaus OK. Man muss nicht 
immer mit einem Timer arbeiten, wenn gleich es gerade beim Multip0lexing 
der Standardansatz ist.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

kyrk.5 schrieb:
> Faustregel: alles was delay_ms oder delay_us heisst muss aus der C Code
> raus!

Nö. Es hält sich hartnäckig das Gerücht, das diese beiden Funktionen 
'blocking' sind, dem ist aber nicht so. Etwaige Interrupts werden 
nämlich bedient und deswegen kann _delay_xx() durchaus sinnvoll sein.
Es muss lediglich F_CPU definiert sein, bevor util/delay.h eingebunden 
wird.

Allerdings ist ein Timerinterrupt für das Multiplex Display immer 
vorzuziehen, vor allem ist es dann ein 'Fire-and-Forget' Ding ohne 
Flimmern und als Nebeneffekt hat man dann gleich einen Ticker für z.B. 
Tasten- oder Encoderentprellung.

: Bearbeitet durch User
von Lars (Gast)


Lesenswert?

Über den Sinn und Unsinn von _delay wollte ich eigentlich nicht 
diskutieren. Viel mehr suche ich eine Erklärung für das eigenartige 
Verhalten. Warum passt das Timing bei der SIGNAL_ON_LED, jedoch nicht 
bei den DIGITs?

von Falk B. (falk)


Lesenswert?

@Matthias S. (Firma: matzetronics) (mschoeldgen)

>Nö. Es hält sich hartnäckig das Gerücht, das diese beiden Funktionen
>'blocking' sind, dem ist aber nicht so.

Aber sicher sind sie das. Während der Funktion kommt das Programm nicht 
weiter, die CPU dreht Däumchen.

> Etwaige Interrupts werden
>nämlich bedient

Das ist aber nicht die Definition einer blockierenden Funktion!

von Falk B. (falk)


Lesenswert?

>Testweise habe ichh noch eine "SIGNAL_ON_LED" eingebaut. Wenn ich an
>dieser mit dem Osziloskop messe, stimmen die eingestellten 100 µs
>Einschaltdauer.

Glaub ich nicht, denn dazwischen liegt noch eine Funktion mit einem 
Argument einer Modulo-Operation.

>  PORTC |= (1<<SIGNAL_ON_LED);
>  _delay_us(time_on);
>  PORTB &= ~(1<<DIGIT1);
>  SevenSegment(minute%10);
>  PORTC &= ~(1<<SIGNAL_ON_LED);

>Anbei ein Auszug aus der gekürzten mux_display Funktion.

Dort könnte ein Problem liegen. Zeig uns den ORIGINALEN, unmodifizierten 
Quelltext.

von Lars (Gast)


Lesenswert?

Bitteschön:
1
#define time_on 100
2
#define time_off 300
3
4
5
void mux_display()
6
{
7
  PORTB &= ~(1<<DIGIT4);
8
  SevenSegment(minute/10);
9
  _delay_us(time_off);
10
  PORTB |= (1<<DIGIT1);
11
  PORTC |= (1<<SIGNAL_ON_LED);
12
  _delay_us(time_on);
13
  
14
  
15
  PORTB &= ~(1<<DIGIT1);
16
  SevenSegment(minute%10);
17
  PORTC &= ~(1<<SIGNAL_ON_LED);
18
  _delay_us(time_off);
19
  PORTB |= (1<<DIGIT2);
20
  _delay_us(time_on);
21
  
22
  
23
  PORTB &= ~(1<<DIGIT2);
24
  SevenSegment(dp);
25
  _delay_us(time_off);
26
  PORTB |= (1<<DIGITDP);
27
  _delay_us(time_on);
28
  
29
  
30
  PORTB &= ~(1<<DIGITDP);
31
  SevenSegment(sekunde/10);
32
  _delay_us(time_off);
33
  PORTB |= (1<<DIGIT3);
34
  _delay_us(time_on);
35
  
36
  
37
  PORTB &= ~(1<<DIGIT3);
38
  SevenSegment(sekunde%10);
39
  _delay_us(time_off);
40
  PORTB |= (1<<DIGIT4);
41
  _delay_us(time_on);
42
}

von Karl M. (Gast)


Lesenswert?

Hallo Lars schrieb:
> Bitteschön:
>

> void mux_display()
> {
>   PORTB &= ~(1<<DIGIT4);
>   SevenSegment(minute/10);
>   _delay_us(time_off);
>   PORTB |= (1<<DIGIT1);
>   PORTC |= (1<<SIGNAL_ON_LED);
>   _delay_us(time_on);
:
> }

Nein nicht wirklich, eine geheime Funktion: SevenSegment(...)

von Lars (Gast)


Lesenswert?

Sorry, das ist diese hier:
1
void SevenSegment(uint8_t n)
2
3
{
4
5
  switch (n)
6
  {
7
    case 0:
8
    SEVEN_SEGMENT_PORT=0b10111111; //a,b,c,d,e,f
9
    break;
10
11
    case 1:
12
    SEVEN_SEGMENT_PORT=0b10000110; // b,c
13
    break;
14
15
    case 2:
16
    SEVEN_SEGMENT_PORT=0b11011011; // a,b,d,e,g
17
    break;
18
19
    case 3:
20
    SEVEN_SEGMENT_PORT=0b11001111; // a,b,c,d,g
21
    break;
22
23
    case 4:
24
    SEVEN_SEGMENT_PORT=0b11100110; // a,b,f,g
25
    break;
26
27
    case 5:
28
    SEVEN_SEGMENT_PORT=0b11101101; // a,c,d,f,g
29
    break;
30
31
    case 6:
32
    SEVEN_SEGMENT_PORT=0b11111101; // a,c,d,e,f,g
33
    break;
34
35
    case 7:
36
    SEVEN_SEGMENT_PORT=0b10000111; // a,b,c
37
    break;
38
39
    case 8:
40
    SEVEN_SEGMENT_PORT=0b11111111; // a,b,c,d,e,f,g
41
    break;
42
43
    case 9:
44
    SEVEN_SEGMENT_PORT=0b11101111; // a,b,c,d,f,g
45
    break;
46
47
    case 10:
48
    SEVEN_SEGMENT_PORT=0b10000000; //DP
49
  }
50
}

von Karl M. (Gast)


Lesenswert?

Danke Peter,

kann man so void SevenSegment(uint8_t n); programmieren.
Der Define für SEVEN_SEGMENT_PORT ist da so etwas wie PORT{A,D,E,F,G} ?

von Falk B. (falk)


Lesenswert?

@Lars (Gast)

>Sorry, das ist diese hier:
>void SevenSegment(uint8_t n)


Schon mal was von Arrays gehört?
1
const uint8_t int2seven[10] = {0b10111111, ....};
2
3
SEVEN_SEGMENT_PORT = int2seven[Minute/10];

Das ist nicht nur kürzer und übersichtlicher, es ist auch in der 
Ausführung schneller.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

DDRs korrekt initialisiert? Vielleicht schaltest du nur die Pullups 
(sowas hab ich bei mir auch mal stundenlang gesucht... Pullups sind ein 
Luder...)

von Lars (Gast)


Lesenswert?

Ja, SEVEN_SEGMENT_PORT ist PortD
1
#define SEVEN_SEGMENT_PORT PORTD



In der main() setzte ich alle Pins von PortB und PortD auf Ausgang, 
sollte also auch passen.
1
int main(void)
2
{
3
4
  
5
  DDRB |= 0xff;
6
  DDRC |= (1<<SIGNAL_ON_LED);
7
  DDRD |= 0xff;

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

hast du die zu hohe Zeit auf allen Digits, oder nur auf Digit 4?

von Lars (Gast)


Lesenswert?

Gute Frage, müsste ich heute Abend nochmal schauen. Habe bisher 
tatsächlich nur an Digit4 gemessen.
Könnte also sein, dass die mux_display-Funktion in der while() nicht 
rechtzeitig wieder aufgerufen wird.

von J Zimmermann (Gast)


Lesenswert?

Matthias S.:
> Es muss lediglich F_CPU definiert sein
Falls das der Fehler ist, wird's in Atmel Studio als Warnung 
dokumentiert
mfg

von Wolfgang (Gast)


Lesenswert?

Matthias S. schrieb:
> Nö. Es hält sich hartnäckig das Gerücht, das diese beiden Funktionen
> 'blocking' sind, dem ist aber nicht so. Etwaige Interrupts werden
> nämlich bedient und deswegen kann _delay_xx() durchaus sinnvoll sein.

Aber bestimmt nicht, wenn man auf genaues Timing wert legt. Für "xx" 
sollte dann insbesondere nicht "us" stehen, sonst wundert man sich über 
irgendwelche Ausreißer bei der Länge der Verzögerungszeit.

von Carl D. (jcw2)


Lesenswert?

Wolfgang schrieb:
> Matthias S. schrieb:
>> Nö. Es hält sich hartnäckig das Gerücht, das diese beiden Funktionen
>> 'blocking' sind, dem ist aber nicht so. Etwaige Interrupts werden
>> nämlich bedient und deswegen kann _delay_xx() durchaus sinnvoll sein.
>
> Aber bestimmt nicht, wenn man auf genaues Timing wert legt. Für "xx"
> sollte dann insbesondere nicht "us" stehen, sonst wundert man sich über
> irgendwelche Ausreißer bei der Länge der Verzögerungszeit.

Man muß sich einfach darüber im Klaren sein, daß auch wenn F_CPU zum 
Quarz und der DIV8-Fuse paßt, nicht genau n μs vergehen, sondern 
mindestens so viele.
Angenommen F_CPU=1MHz, dann führt _delay_us(100); mehr oder weniger 
genau 100 Takte "Blind-Code" aus. Wenn aber ein Int dazwischen kommt, 
dann dauert die "Lücke" zwischen zwei Befehlen auch mal deutlich länger 
als geplant "Null". Ein minimal Int dauert 12Takte (Tiny25), falls IRET 
direkt in der ISR-Tabelle steht.
Wenn man so darauf wartet, daß sich die Datenleitungen zum LCD 
stabilisieren, dann ist es egal, ob 10 oder 20μs. Als Uhr taugt das aber 
nicht.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Lars schrieb:
> Gute Frage, müsste ich heute Abend nochmal schauen. Habe bisher
> tatsächlich nur an Digit4 gemessen.
> Könnte also sein, dass die mux_display-Funktion in der while() nicht
> rechtzeitig wieder aufgerufen wird.

Und?

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.