Forum: Mikrocontroller und Digitale Elektronik Experten gefragt: 8051 mit zwei Timern


von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Hallo!

Ich programmiere beruflich und kenne den 8051-Typ AT89C51CC03 von Atmel 
ziemlich gut, stehe hier aber vor einem Rätsel. Folgendes ist gegeben:
1. Timer 0 auf 20ms, Timer 1 auf 1ms, beide 16 Bit und auch sonst 
identisch konfiguriert.
2. Timer 0 hat Prio 0 und Bank 0, Timer 1 hat Prio 3 und Bank 3
3. Timer 0 dient als Zeitgeber für Zeitkreise >20ms
4. Timer 1 refresht ein 4stelliges LED-Display

Wenn ich nun allen anderen Programmcode ausblende, so daß nur die beiden 
Timer laufen, in ihre Interruptroutinen springen, dort gestoppt, neu 
geladen und wieder gestartet werden, dann flackert die LED-Anzeige, weil 
Timer 1 "gestört" wird. Obwohl er die höchste Prio hat, entstehen 
Zeitlücken von ca. 20ms. Im Emulator (DProbeHS von Hitex) kann ich den 
Code stoppen und Timer 0 anhalten. Danach flackert die Anzeige nicht 
mehr und der Interrupt von Timer 1 wird konstant mit 1ms angesprungen.
Diese Wechselwirkung ist mir und meinen Kollegen völlig neu und 
unverständlich. Alle anderen Interruptquellen wurden testweise 
ausgeschaltet und sind nicht die Ursache. Timer 0 scheint also Timer 1 
zu stören, obwohl es von der Logik her nicht sein dürfte.

Hat das schon mal jemand erlebt und dafür eine Lösung gefunden?
Wäre sehr dankbar, denn wir zerbrechen uns hier schon seit zwei Tagen 
den Kopf darüber.

von Peter D. (peda)


Lesenswert?

Wenns flackert, entspricht der Code nicht Deiner Umschreibung.
Den echten Code posten ist daher immer besser.

Einige Tips:
1.
Laß das Bank Zuweisen weg, wenn Du dafür keinen triftigen Grund nennen 
kannst.
Du mußt dann alle daraus aufgerufenen Funktionen auch auf die Bank 
setzen bzw. bankunabhängig machen.
In der Regel spart es auch nichts.

2.
Es ist ungenau, dem Timer einen festen Wert zuzuweisen, denn damit wird 
die Interruptlatenz vergessen. Dein Timer läuft also prinzipiell 
langsamer.
Abhilfe: Du addierst den Wert zum Timer.
Der Timer enthält nämlich exakt die nach dem Überlauf bis zur Behandlung 
vergangene Zeit.
Die für die Addition nötige Zeit (entnimmt man dem Assemblerlisting) muß 
man noch abziehen.


Peter

von Ralf (Gast)


Lesenswert?

1ms für einen Display-Refresh? Wozu das denn? Oder wird das Display 
gemultiplext?

Timer 0 und Timer 1 werden über die gleichen Register gesteuert, kann es 
sein, dass hier was schief läuft?

Ralf

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Danke für die Tips.

Peter Dannegger schrieb:
> 1.
> Laß das Bank Zuweisen weg, wenn Du dafür keinen triftigen Grund nennen
> kannst.
> Du mußt dann alle daraus aufgerufenen Funktionen auch auf die Bank
> setzen bzw. bankunabhängig machen.
> In der Regel spart es auch nichts.

Ums Sparen geht's hier nicht. Die unterschiedlichen Bänke für 
Interruptroutinen sind nötig. Vor allem, wenn Prio 3 die Prio 2 
unterbricht. Ich kann Timer 1 natürlich nicht sperren, wenn der 
Prozessor
im Interrupt von Timer 0 ist. Das paßt schon. Mache das nicht zum ersten 
Mal.

> 2.
> Es ist ungenau, dem Timer einen festen Wert zuzuweisen, denn damit wird
> die Interruptlatenz vergessen. Dein Timer läuft also prinzipiell
> langsamer.
> Abhilfe: Du addierst den Wert zum Timer.
> Der Timer enthält nämlich exakt die nach dem Überlauf bis zur Behandlung
> vergangene Zeit.
> Die für die Addition nötige Zeit (entnimmt man dem Assemblerlisting) muß
> man noch abziehen.

Das ist nicht das Problem. Natürlich mache ich das so.

Hmm, vielleicht drücke ich mich unklar aus.

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Ralf schrieb:
> 1ms für einen Display-Refresh? Wozu das denn? Oder wird das Display
> gemultiplext?
>
> Timer 0 und Timer 1 werden über die gleichen Register gesteuert, kann es
> sein, dass hier was schief läuft?
>
> Ralf

Gemultiplext, ja. Macht man meistens so. Du meinst die SFRs? Nein, läuft 
nichts schief. Wir machen das schon ein paar Jahre und kennen diesen 
Atmel in- und auswendig. Nur, so ein Problem hatten wir nie.
Man kann mit den paar Registern nicht viel falsch machen. Außerdem ist 
der Hitex-Emulator in der Lage, mir anzuzeigen, was die Register 
darstellen. Und da stimmt alles.

Nochmal in anderen Worten:
Prozessor startet -> Timer werden initialisiert und gestartet -> Timer 1 
Interrupt schreibt pro Durchlauf eins der 4 LED-Segmente, also 4ms für 
alle (250Hz) -> Anzeige flackert mehrfach pro Sekunde weil der Timer 1 
in seiner Ausführung gestört wird -> Prozessor anhalten, Timer 0 stoppen 
-> Timer 1 läuft ohne Störung und Anzeige flackert nicht mehr.

Zusatz: Selbst wenn ich alle anderen Interrupts ausschalte, also auch 
den von Timer 0, flackerts. Das entsteht nur dadurch, daß beide Timer 
gleichzeitig laufen.

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Ralf schrieb:
> 1ms für einen Display-Refresh? Wozu das denn? Oder wird das Display
> gemultiplext?

Ach so, ganz vergessen. Mehrere Gründe: zum Einen teste ich noch, zum 
Anderen läßt sich damit die Helligkeit regeln. Sind bei 1ms eh schon zu 
hell.
Ist halt ne Softwarelösung, damit man die Hardware nicht nachträglich 
ändern muß. Leider hatte damals keiner einen PWM zur 
Helligkeitssteuerung hergenommen.

von Thomas (Gast)


Lesenswert?

wenn das so ist wie du beschreibst kann das nur daran liegen, dass dein 
Display Interrupt nicht auf der hohen Priorität läuft.

Lösungen:
Entweder die Prioritäten richtig vergeben oder den Display Interrupt
an Timer 0 hängen.

Der Fehler kommt meines Erachtens daher, dass dein 20ms Interrupt den 
Mux Interrupt unterbricht. Wir Peter bin ich allerdings auch der 
Meinung, das ein Regbank Switch nur in Ausnahmefällen was bringt.

Thomas

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

1
#define RELOAD_T1_LOW (RELOAD_T1%256)
2
#define RELOAD_T1_HIGH (RELOAD_T1/256)
3
4
static void ISR_Timer1(void) interrupt TIMER1_vektor using TIMER1_USING_REG
5
{
6
   TR1=0;
7
  TL1+=RELOAD_T1_LOW;
8
  if(!CY)
9
  {
10
       TH1+=RELOAD_T1_HIGH;
11
  }
12
  else
13
        {  
14
       TH1+=(RELOAD_T1_HIGH+1);
15
  }
16
  TR1=1;
17
  v_Refresh_Display();
18
}

Der Code für Timer 0 ist vom Reload her identisch.

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Thomas schrieb:
> wenn das so ist wie du beschreibst kann das nur daran liegen, dass dein
> Display Interrupt nicht auf der hohen Priorität läuft.
>
> Lösungen:
> Entweder die Prioritäten richtig vergeben oder den Display Interrupt
> an Timer 0 hängen.
> Thomas

Danke für die Hilfe, aber bitte richtig lesen. Timer 1 hat Prio 3 und 
Timer 0 hat Prio 0. Werde morgen mal einen Screenshot von Hitop 
einbinden, bin gerade auf einem anderen Rechner.

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Angehängte Dateien:

Lesenswert?

Hitop-Screenshot, wo man Prio und Timer-Settings sehen kann.
Da nicht jeder Hitop/Hitex kennt: das ist, zusammen mit der Hardware 
DProbeHS ein 100%iger Emulator. Daher kann ich auch den Prozessor 
anhalten und z. B. Timer 0 stoppen/starten.

von Peter D. (peda)


Lesenswert?

Maik Staberock schrieb:
> Ums Sparen geht's hier nicht. Die unterschiedlichen Bänke für
> Interruptroutinen sind nötig. Vor allem, wenn Prio 3 die Prio 2
> unterbricht.

Das ist Quatsch. Die Bankzuweisung sagt dem Compiler nur, daß er die 
Register nicht sichern muß. Bringt aber nur was, wenn der Interrupt auch 
viele Register benötigt.

Die Bankzuweisung kann aber böse Seiteneffekte haben, wenn der Interrupt 
Unterfunktionen aufruft. Und wenn verschiedene Level die gleiche Bank 
benutzen.


Peter

von Peter D. (peda)


Lesenswert?

Maik Staberock schrieb:
> Zusatz: Selbst wenn ich alle anderen Interrupts ausschalte, also auch
> den von Timer 0, flackerts. Das entsteht nur dadurch, daß beide Timer
> gleichzeitig laufen.

Dann mußt Du mal ein komplettes flackerndes Programm zeigen.

Ich benutze auch regelmäßig beide Timer und die haben sich noch nie 
beeinflußt.
Ich hab auch schon T0,T1 im 3-Timer Mode benutzt (TL0,TH0,T1), keine 
Probleme.


Hast Du alle 5 Timer des CC03 in Benutzung (T0,T1,T2,PCA,CANTIM)?


Peter

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Peter Dannegger schrieb:
> Das ist Quatsch. Die Bankzuweisung sagt dem Compiler nur, daß er die
> Register nicht sichern muß. Bringt aber nur was, wenn der Interrupt auch
> viele Register benötigt.

Ahso. Dann machen wir Programmierer das sicher nur seit Jahren falsch.
Warum macht man eine Bankzuweisung bei einem Interrupt? Weil ein 
Interrupt, der eine niedrigerpriorisierten unterbricht, dessen 
Registerdaten verändern würde! Warum sonst? Das kann der Compiler nicht 
wissen.
Natürlich verwenden nur gleiche Level die gleiche Bank. Und für die 
Unterroutinenaufrufe haben wir ja noch #pragma NOAREGS. Das paßt schon, 
seit Jahren. Mit mehreren Leveln und vielen Hardware-Interrupts, auf 
vielen Geräten.

Die Bankzuweisung mag dem Compiler zwar sagen, daß er die Register nicht 
sichern muß, aber a) isses sicherer so und b) schneller, weil er nur die 
Bank umschalten muß und nicht die ganze Registerbank auf den Stack 
schieben.

Abgesehen davon ist das nicht das Problem hier. Sonst wäre das einfacher 
zu lösen.

von Bernd N (Gast)


Lesenswert?

Kann Peter nur zustimmen, eine Bankumschaltung sollte man sich schenken.

von Ralf (Gast)


Lesenswert?

> Wir machen das schon ein paar Jahre und kennen diesen
> Atmel in- und auswendig. Nur, so ein Problem hatten wir nie.
Okay, bezweifelt hier auch keiner, dass Erfahrung vorhanden ist (ein 
Anfänger hat sicher keinen Zugriff auf einen Emulator :)
Aber ich habe ebenfalls jahrelange Erfahrung, und bin auch schonmal auf 
die einfachen Sachen nicht gekommen ;)

Deswegen frage ich einfach mal, ob Punkt 3 aus 
http://www.atmel.com/dyn/resources/prod_documents/doc4293.pdf hier eine 
Rolle spielen könnte?

Ralf

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Peter Dannegger schrieb:
> Dann mußt Du mal ein komplettes flackerndes Programm zeigen.

Nee, das wäre zuviel für hier. Ich schrieb doch oben, daß ich alles 
andere abschalten kann im Emulator, meinetwegen auch per Code. Der 
Effekt ist derselbe. Es laufen nur noch Timer 1 und Timer 0. Interrupts 
von anderen Quellen stören nicht, aber selbst wenn nur Timer 0 Interrupt 
erlaubt ist, tritt der Fehler noch auf. Wie gesagt, er tritt auf, sobald 
Timer 0 nur läuft.

> Hast Du alle 5 Timer des CC03 in Benutzung (T0,T1,T2,PCA,CANTIM)?

Nein. CAN ist momentan deaktiviert, PCA läuft auf 1/2 Clock. T2 kann ich 
anhalten -> der Fehler bleibt. Sobald ich T0 stoppe, flackert nichts 
mehr.

Das "Flackern" äußert sich in ungleichmäßiger Dauer (10-20ms sind die 
LED-Anzeigen aus) und Abständen, tritt aber mehrmals pro Sekunde auf.

von Peter D. (peda)


Lesenswert?

Maik Staberock schrieb:

> Ahso. Dann machen wir Programmierer das sicher nur seit Jahren falsch.

Nicht direkt falsch.
Wenn der Interrupt nur 2 Register verwendet, wird trotzdem eine ganze 
Bank reserviert. Du verschenkst also 6 Byte (5%) schnellen direkten 
SRAM, die woanders vielleicht nützlicher wären.


> Warum macht man eine Bankzuweisung bei einem Interrupt? Weil ein
> Interrupt, der eine niedrigerpriorisierten unterbricht, dessen
> Registerdaten verändern würde! Warum sonst? Das kann der Compiler nicht
> wissen.

Falsch!
Der Compiler sichert alle verwendeten Register.
Erst mit der Bankzuweisung schaltest Du das ab.
Du bist dann aber in der Pflicht, Dich selber darum zu kümmern, daß 
keine andere Priorität die selbe Bank benutzt.
Auch hast Du Dich darum zu kümmmern, daß Unterfunktionen nicht 
versehentlich Bank 0 annehmen.
Du hast dem Compiler ja die Registersicherung abgeschaltet.


> Und für die
> Unterroutinenaufrufe haben wir ja noch #pragma NOAREGS.

Einmal vergessen und Peng.
Ich bin da mal mit reingefallen, seitdem lasse ich das.

Der Compiler macht keine Schusselfehler. Also ist es deutlich sicherer, 
ihm die Registerverwaltung auch im Interrupt zu überlassen.


> Abgesehen davon ist das nicht das Problem hier. Sonst wäre das einfacher
> zu lösen.

Ohne Code lassen sich solche Probleme fast nie lösen.


Peter

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

@Peter

Da ist was dran. Naja, mein Softwareleiter sieht das sicher anders, aber 
so wie wir es machen, geht's auch. Dein System ist nur einfacher.

von Reinhard Kern (Gast)


Lesenswert?

Hallo,

was ich nicht verstehe: ein Hitex-Emulator kann doch sicher tracen, 
meiner konnte das schon vor 25 Jahren. Also mach ein Trace vom 
Programmablauf, da kannst du zweifelsfrei nachlesen, ob während der ISR 
von Timer 1 ein Timer 0 Interrupt auftritt, bzw. ob aus T1 ISR in T0 ISR 
gesprungen wird. Warum hier lange diskutieren, wenn du nur nachschauen 
müsstest?

Gruss Reinhard

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Ralf schrieb:
> Deswegen frage ich einfach mal, ob Punkt 3 aus
> http://www.atmel.com/dyn/resources/prod_documents/doc4293.pdf hier eine
> Rolle spielen könnte?

Oh-oh... Das war es. Wie du schon sagtest, manchmal kommt man auf die 
einfachsten Sachen nicht. Shame on me!

Super, vielen Dank euch! Selbst mein Softwarechef ist da nicht drauf 
gekommen.

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Reinhard Kern schrieb:
> Hallo,
>
> was ich nicht verstehe: ein Hitex-Emulator kann doch sicher tracen,

Ja, mit der DBox zusammen. Aber die ist sehr umständlich und da ich die 
so gut wie nie brauche, kenne ich mit damit nicht aus.

von Helmut L. (helmi1)


Lesenswert?

Warum 2 Timer Interrupts ?
Das ganze kann man doch auch mit dem 1mS Timer Interrupt erschlagen. 
Dort eine Variable bis 20 zaehlen lassen und dann den Code fuer die 20mS 
Routine ausfuehren. Und schon hast du ein Problem weniger.

Gruss Helmi

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

Helmut Lenzen schrieb:
> Warum 2 Timer Interrupts ?
> Das ganze kann man doch auch mit dem 1mS Timer Interrupt erschlagen.
> Dort eine Variable bis 20 zaehlen lassen und dann den Code fuer die 20mS
> Routine ausfuehren. Und schon hast du ein Problem weniger.

Generell eine gute Idee. Aber wenn die Ausführung des Codes vom 20ms 
Timer nun selbst 2ms dauern würde, dann... ;o)

von Helmut L. (helmi1)


Lesenswert?

Maik Staberock schrieb:
> Generell eine gute Idee. Aber wenn die Ausführung des Codes vom 20ms
> Timer nun selbst 2ms dauern würde, dann... ;o)

Dann nuetzen dir auch 2 Timer nix. Denn die Zeit muss ja irgendwo 
herkommen. Der Prozessor teilt sich ja nicht auf. Und genau da wird dein 
flackern herkommen. Nur das das mit einem Timer immer syncron auftritt. 
Du muesstes dann die eine Routine die 2mS verbraucht in kleinere 
Haeppchen zerlegen und die in den 1mS verschachteln.

Gruss Helmi

von Maik S. (Firma: Elektro-Automatik) (mstaberock)


Lesenswert?

@Helmi
Mein Problem ist doch gelöst, wie ich oben schrieb. Es ist zwar 
unlogisch, dieses Verhalten, aber ein Bug des Prozessors.

Zu deiner Aussage: Klar teilt sich der Prozessor auf, sozusagen. Der 
Interrupt mit Prio3 unterbricht den Interrupt mit Prio 0.
Wenn ich im Interrupt des 20ms-Timers Code aufrufe, dessen Ausführung, 
sagen wir mal, 5ms benötigt, dann würde dein Vorschlag nicht 
funktionieren. Der separate 20ms-Timer hat damit kein Problem. Bei mir 
ist der 1ms-Timer höherpriorisiert und nur für das Refreshing der 
Anzeige zuständig. Der 20ms Timer wiederum ist Zeitbasis für Zeitkreise 
bis 65535 * 20ms, was einen guten Spielraum darstellt. Der 1ms-Timer ist 
jetzt neu in Gebrauch, um die Anzeige ruhiger zu machen. Vorher wurde 
die einmal pro Main()-Durchlauf aktualisiert und der ist ca. 2ms. Da es 
aber nicht immer genau 2ms waren, traten leichte Helligkeitsschwankungen 
auf. Jetzt nicht mehr.

von Peter D. (peda)


Lesenswert?

Maik Staberock schrieb:
> Der 1ms-Timer ist
> jetzt neu in Gebrauch, um die Anzeige ruhiger zu machen. Vorher wurde
> die einmal pro Main()-Durchlauf aktualisiert und der ist ca. 2ms. Da es
> aber nicht immer genau 2ms waren, traten leichte Helligkeitsschwankungen
> auf. Jetzt nicht mehr.

Ich habs noch nie probiert, ne Multiplexanzeige ohne Timerinterrupt zu 
machen. Ich kenne derartige Probleme daher nicht.

Man sieht aber öfters Fragen von Anfängern, warum es flackert oder 
Digits unterschiedlich hell sind. Ein Blick auf die Source verrät dann 
sofort, warum.
In der Regel gehen damit auch Probleme bei der Tastenentprellung Hand in 
Hand.


Die Erratas mit der Takteinstellung sind mir auch schon aufgefallen. Ich 
betreibe daher immer CPU und Peripherie mit der selben Taktrate.


Peter

von Peter D. (peda)


Lesenswert?

Helmut Lenzen schrieb:
> Dann nuetzen dir auch 2 Timer nix. Denn die Zeit muss ja irgendwo
> herkommen. Der Prozessor teilt sich ja nicht auf.

Der höher priorisierte Interrupt nimmt sie sich einfach.
Er kann beliebig oft das Main und alle niedrigeren Interrupts 
unterbrechen.


Das ist anders, als z.B. beim AVR. Da müssen sämtliche Interrupts 
aufeinander Rücksicht nehmen. Keiner kann einen anderen unterbrechen.
Die maximale Interruptlatenz ergibt sich beim AVR daher aus der Summe 
sämtlicher Interrupthandler.
Da gilt also ausnahmslos: fasse Dich kurz.


Peter

von Reinhard Kern (Gast)


Lesenswert?

Peter Dannegger schrieb:
>> Der höher priorisierte Interrupt nimmt sie sich einfach.
> Er kann beliebig oft das Main und alle niedrigeren Interrupts
> unterbrechen.
> Das ist anders, als z.B. beim AVR. Da müssen sämtliche Interrupts
> aufeinander Rücksicht nehmen. Keiner kann einen anderen unterbrechen.

Hallo Peter,

das ist ein wichtiger Unterschied in verschiedenen Architekturen (und 
oft ein übersehenes Problem), aber man ist dem nicht auf Gedeih und 
Verderb ausgeliefert. Man kann z.B. beim mit etwas Tricks auf dem Stack 
die ISR gleich wieder verlassen, aber an eine Programmstelle 
zurückkehren, die als normales Unterprogramm ausgeführt wird (und tut 
was die ISR tun soll) und an die unterbrochene Stelle des Hauptprogramms 
zurückkehrt. Dabei kann man Interrupts wieder zulassen und so nested 
Interrupts per Software erzeugen, die der Prozessor garnicht kann.

Voraussetzungen:
1. genaues Verständnis der Interrupt Steuerung und Priorisierung
2. genaue Kenntnisse über Subroutinen/ISR-Aufrufe, Rückkehr und 
Stackverwaltung dabei.

Umgekehrt ist es einfacher: man kann ja zum Beginn einer ISR alle IRQs 
abschalten und erst am Ende wieder zulassen.

Klar ist, dass ein Programm mit nested Interrupts wesentlich komplexer 
abläuft und daher auch viel mehr Fehler enthalten kann.

Gruss Reinhard

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.