das mit dem sei() ist mir zwar immer noch ganz klar, wann und wo man setzen muss
Mir ist nicht klar, wo Du ein Verständnisproblem hast. Deswegen schieb ich jetzt einfach mal ein paar Grundlagen dazwischen.
Die AVR besitzen einen seperaten Programmspeicher (Harvard-Architektur). Dieser ist linear, also eine Liste Words. In jedem Word steht eine Maschinencode-/Assemblerinstruktion (bis auf ganz wenige Ausnahmen, die mehr als ein Word benötigen).
Außerdem gibts einen Programmzähler, der auf das Word mit der nächsten abzuarbeitenden Instruktion zeigt.
Beim Reset wird (unter anderem) dieser Programmzähler auf Null gesetzt (Ausnahme Bootloader?), der Controller beginnt also bei Adresse Null mit der Abarbeitung des Program-Flash-Inhaltes. Die Instruktion wird Verarbeitet, der Programmzähler dabei (automatisch) inkrementiert.
Bei einem Sprung (im Sinne von Goto) wird einfach der entsprechende Wert in den Programmzähler geschrieben.
Bei einem Funktionsaufruf (Call Subroutine/Gosub/...), also wo ein Rücksprung erfolgen muß, muß die Rücksprungadresse gesichert werden. Dazu wird am Ende des SRAM ein Stapel eingerichtet, der von hinten nach vorn wächst (und von vorn nach hinten schrumpft). Beim Aufruf der Funktion wird also die Adresse der nächsten Instruktion auf diesen Stapel gelegt, und die Adresse der Funktion in den Programmzähler geladen. Beim Rücksprung aus der Funktion (Return) wird die Adresse vom Stapel genommen, und in den Programmzähler geladen. Es ist also möglich, mehrere Funktionsaufrufe (auch derselben Funktion) ineinander zu schachteln - solange der Speicher für den Stapel reicht.
So, nach einem Reset beginnt der Controller wie gesagt mit Flash-Adresse Null. Die folgenden Adressen sind mit dem Interrupt-Mechanismus verbunden, deswegen steht in Adresse Null meist ein Sprung in das "Hauptprogramm".
Der Controller arbeitet den Flash-Inhalt also eigentlich linear (mit etwaigen Verzweigungen und Sprüngen) nacheinander ab. Ein Thread. Periphäre Hardware kann aber parallel dazu selbständig arbeiten (die Timer laufen im Hintergrund, und lassen ggf bei PWM die Beine Zappeln, der UART sendet und empfängt bitweise Bytes, der ADC mißt Spannungen, SPI und TWI pumpen/saugen Bytes durch ein Bein usw).
Alle diese Hardware-Module hinterlegen Dir für Dein Programm Informationen in ihren I/O-Registern (manchmal auch Special Function Register oder so genannt).
Du kannst diese Informationen also im Hauptprogramm zyklisch abfragen (pollen).
Oder eben Interrupts nutzen. Was passiert dabei konkret?
Oben hatte ich das Datenblatt des Tiny13 verlinkt, schau mal Kapitel 9 (S. 44) an.
Jede Interrupt-Quelle (beim Tiny13 sinds neun) ist fest mit einer Flash-Adresse verbunden.
Jede Quelle muß erstmal mit einem eigenen Enable-Bit scharfgemacht werden (für den externen Interrupt hast Du das mit "INT0" im "GIMSK" gemacht, für den Timerüberlauf mit "TOIE0" im "TIMSK").
Tritt ein interruptauslösendes Event ein (Timerüberlauf/Flankenwechsel usw) wird automatisch ein entsprechendes Interruptanforderungsflag gesetzt. Ist der Interrupt scharf, und sind Interrupts global enabled, werden automatisch die Interrupts global gesperrt, Flashadresse der eigentlichen nächsten Instruktion auf den Stapel gepackt und die fest mit dem Interrupt verbundene Flash-Adresse angesprungen (also sozusagen ein call subroutine auf diese Adresse mit gleichzeitigem Unterdrücken/Zurückhalten aller Interrupts - außerdem wird dabei meist das Interrupt-Anforderungsflag gelöscht).
Da eine ISR im allgemeinen nicht in diese fest verbundene Adresse paßt, steht dort üblicherweise ein Sprung in die eigentliche Service-Routine, ein Vektor. Deswegen nennt man diesen Flash-Bereich auch InterruptVectorTabelle.
Em Ende der ISR steht dann ein Return from Interrupt, welches die Rücksprungadresse vom Stapel nimmt und anspringt, und gleichzeitig die Interrupts global wieder scharfmacht.
Ein Interrupt kann also normalerweise nicht von einem anderen Interrupt unterbrochen werden.
Bei etwaigen zurückgehaltenen Interrupts, von inzwischen eingetretenen Interruptereignissen, ist ja immer noch das Anforderungsflag gesetzt, sobald die Interrupts global wieder freigegeben werden, werden die wartenden Interrupts wirksam. Die Priorität entspricht der Reihenfolge in der Interruptvektortabelle.
Ob die Interrupts global scharf sind oder nicht, legt das "I"-Bit im Statusregister fest. Dieses kann (neben der automatischen Manipulation durch den Interruptmechanismus) mit SEI (set I) gesetzt werden, mit CLI (clear I) gelöscht werden.
Das macht zB Sinn, wenn eine Operation mit einer Variable mit mehr als einem Byte durch einen Interrupt unterbrochen werden könnte, welcher die Variable zwischendurch verändert. Die AVR arbeiten ja mit einer 8bit-Aritmetik. Um also zwei Words zu addieren, werden zwei Byteadditionen benötigt. Zwischen den beiden Byteadditionen könnte ein Interrupt zuschlagen, der eine der Variablen verändert.
Um so eine Operation unteilbar - atomar - zu bekommen, unterdrückt man also vorher global alle Interrupts (CLI) und macht sie ggf hinterher wieder scharf (SEI).
Also zurück zur Frage, wann die Interrupts global scharfzumachen sind: dann, wenn Du sie das erste mal brauchst, im allgemeinen also erst, wenn alle verwendeten Interruptquellen initialisiert sind.
In dem Zusammenhang fällt mir bei Deinem Code (#18) auf, daß Du den externen Interrupt in der Timerüberlaufs-ISR initialisierst, und zwar bei jedem Aufruf (also bei jedem Überlauf). Da der Timer mindestens einmal überläuft, funktioniert das natürlich, da die Werte der drei Bits in GICR und MCUCR sich nicht ändern stört das ständige Überschreiben (mit denselben Werten) auch nicht weiter - es kostet nur unnötig Zeit.
Es wäre sinniger, das einmal wie beim Timer in der main zu machen.
Ebenso das setzen des Beines als Eingang - das muß auch nicht ständig in der while(true)-Schleife wiederholt werden...
Was soll eigentlich Zeile 42? Der Mega8 hat doch gar kein GIMSK. INT0 bis zum nächsten Timerüberlauf abschalten (eigentlich in GICR)?
Zur Displayausgabe kann ich erstmal nichts weiter sagen, bezüglich der Siebensegmente sehe ich keinen Code, ein TWI-LCD wäre komplexer, wobei Du sicher vorgefertigte C-Routinen verwenden würdest.
Aber nochwas zu Deiner Warteschleife mit "z":
Der Controller rennt mit 8MHz.
Das setzen des Beines als Eingang sollte C mit drei Takten umsetzen (oder gut optimiert mit einem Takt -
@Dirk macht C daraus "IN->ANDI->OUT" oder "CBI"?)
Das "z++" sollten fünf weitere Takte sein ("LDS->INC->STS")
Das "If..." schlägt mMn mit vier Takten zu ("LDS->CPI->BRanch")
Also über den Daumen 10-20 Takte, das ganze hundertmal - dann würde disp_Zähler alle 1000-2000 Takte aufgerufen werden, also mit 4-8kHz.
Wie von Dirk angedeutet, verzögern die Interrupts das ganze nochmal ein wenig.