N76E003 Timer und Interupt
Für
viele Aufgaben hat es sich bewährt, dass einige Teile im Hauptprogramm laufen
und andere im Interrupt. So kann man z.B. eine Multiplexanzeige realisieren,
die völlig flackerfrei im Interrupt gesteuert wird, während im Hauptprogramm
z.B. serielle Daten verarbeitet werden. Hier
soll der Timer 0 mit 4 kHz laufen und eine Timer-Interruptfuntion steuern. Alle
Einstellungen sollen den bereits für den UART einstellten Timer 0 unverändert
lassen, damit die serielle Schnittstelle weiterhin funktioniert.
Die
Eigenschaften der Timer werden kompatibel zum alten 8051 über die Register TCON
(SFR 88h) und TMOD (SFR 89h) kontrolliert. Dabei wird die
Betriebsart über TMOD eingestellt.
TMOD (SFR
89h)
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
Gate
|
C/T
|
M1
|
M0
|
Gate
|
C/T
|
M1
|
M0
|
Zähler
1
|
Zähler
0
|
Gate: Der jeweilige Zähler wird über den
Anschluss INT0 bzw. INT1 freigegeben.
C/T 0: Timer, 1: Counter
M1
und M0: Betriebsart
0
0 13-Bit-Zeitgeber/Zähler
0
1 16-Bit-Zeitgeber/Zähler
1
0 8-Bit-Zitgeber/Zähler,
automatisches Nachladen
1
1 Nur für Zähler 0: zwei
getrennte 8-Bit-Zähler
Timer 1 soll für den UART als freilaufender Zähler die internen Taktimpulse des 8051 zählen. Er
wird daher als Timer in der Betriebsart 2 eingerichtet. Bei jedem Überlauf wird
dabei ein voreingestellter Zählerstand erneut in den Zähler übernommen. Jeder
Zähler wird über sein TR-Bit im Register TCON gestartet. TCON enthält auch die
Überlauf-Flags beider Timer und vier Steuerbits zur Interruptsteuerung über die
Anschlüsse INT0 und INT1.
TCON (SFR 88h)
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
TF1
|
TR1
|
TF0
|
TR0
|
IE1
|
IT1
|
IE0
|
IT0
|
TF1: Überlauf-Flag für Timer 1
TR1: Einschalten von Timer 1
TF0: Überlauf-Flag für Timer 0
TR0: Einschalten von Timer 0
IE1: Interrupt an INT1 mit Flankentriggerung
IT1: Interrupt-Flag für INT1
IE0: Interrupt an INT0 mit Flankentriggerung
IT0: Interrupt-Flag für INT0
Zusätzlich gibt es beim N76 Einstellungen
zur Taktfrequenz mit den Bits T1M und T0M für die beiden Timer 0 und 1. Beide
Bits sind nach einem Reset Null, sodass sich die Timer wie beim originalen 8051
verhalten. Die Taktfrequenz wird dabei durch 12 geteilt. Setzt man jedoch das
entsprechende M-Bit, erhält der Timer den vollen Takt von 16 MHz.
CKCON (SFR 88h)
7
|
6
|
5
|
4
|
3
|
2
|
1
|
0
|
-
|
PWMCKS
|
-
|
T1M
|
T0M
|
-
|
CLOEN
|
-
|
Der Timer1 wird bereits für die serielle
Schnittstelle benutzt und dazu in der Funktion
InitUART0_Timer1 intialisiert. Hier wird der Timer-Modus 2 verwendet. Außerdem
wird T1M gesetzt, sodass der volle Takt von 16 MHz verwendet wird :
void
InitUART0_Timer1(long Baudrate) //T1M
= 1, SMOD = 1
{
SCON = 0x50; //UART0 Mode1,REN=1,TI=1
TMOD |= 0x20; //Timer1 Mode2 , 8-Bit-Zitgeber/Zähler,
automatisches Nachladen
PCON |= 128; //SMOD = 1 UART0 Double Rate Enable
CKCON |=16; //set_T1M, 16 MHz
T3CON &= ~64; //BRCK = 0 Serial port 0 baud rate clock source = Timer1
TH1 = 256 - (1000000/Baudrate+1); /*16 MHz */
TR1 = 1;
TI = 1; //For printf
}
Für eine Übertragungsgeschwindigkeit von
9600 Baud braucht der UART einen Takt von 16 x 9,6 kHz = 153,6 kHz,
dazu muss der Timer die 16 MHz durch 104 teilen. 16 MHz / 16 / 104 =
9,615 kHz. Die Teilung nur mit einem 8-Bit-Register reicht aus, weil
der Takt ohnehin nur eine Genauigkeit von rund 1% hat und die Baudrate
nur auf ca. 3% genau eingehalten werden muss.
Hier wird der Timer0 zuerst als 16-Bit-Timer (Modus 1) für die
Interruptroutine verwendet, während der Timer1 im Modus 2 (8 Bit,
Auto-Reload) für die Baudratenerzeugung des UART zuständig bleibt.
Timer0 erhält per Default den Prozessortakt geteilt durch 12. Insgesamt
muss der Takt von 16 MHz durch 4000 geteilt werden, damit am Ende 4 kHz
herauskommt. Durch den Vorteiler /12 verringert sich der Teilerfaktor
auf 4000 / 12 = 333 = 0x014D. Weil der 16-Bit-Timer aufwärts zählt,
muss er jeweils bei 0x10000-0x024D = 0xFEB3 starten. Mit dem Startwert
0xFEB3 ergibt sich also eine Überlaufrate von 4 kHz. Zusätzlich
muss der Tim0-Interrrupt ET0 eingeschaltet werden sowie der globale
Interrupt EA.
Zum Test wird der Zustand von P1.0 in der Interruptroutine getoggelt.
Am Ausgang erscheint damit ein symmetrisches Rechtecksignal von 2 kHz.
Gleichzeitig läuft im Hauptprogramm eine analoge Messung mit serieller
Ausgabe. Damit ist klar, dass der UART korrekt weiter läuft.
// ADC2.c ADC, UART 9600 und Interrupt 4 kHz
#include <N76E003.h>
#include <stdio.h>
void InitUART0_Timer1(long Baudrate);
char getchar(void);
void putchar(char);
void delay(void);
#define TH0_INIT 0xFE;
#define TL0_INIT 0xB3; // 16 MHz / 12 / 333 = 4 kHz
unsigned char p;
void Timer0_ISR (void) __interrupt 1 //interrupt address is 0x000B
{
TH0 = TH0_INIT;
TL0 = TL0_INIT;
p++;
p=p & 1;
P10 =
p;
}
void main(void)
{
unsigned int n;
float u;
P0M1=0;P0M2=0;P1M1=128;P1M2=0; //P17 Eingang
InitUART0_Timer1(9600);
printf ("UART0, 9600 Baud\r\n");
ADCCON1=1;
//ADC on
TMOD=
0x21;
//Timer0 Modus1, Timer1 Modus 2
TH0 = TH0_INIT;
TL0 = TL0_INIT;
ET0 =
1;
//enable Timer1 interrupt
EA =
1;
//enable interrupts
TR0 =
1;
//Timer0 run
while (1)
{
ADCCON0 = 0; //ADC0 = P17
ADCS =
1;
while(ADCF == 0);
n=ADCRH;
n=16 * n+ ADCRL;
printf ("\r\n ADC = %d",n);
u = n * 3000.0 / 4095.0;
n = u;
printf ("\r\n U = %d mV",n);
delay();
}
}
Timer0
im Modus 0
Der
Modus 0 bietet einen 13-Bit-Timer und sollte für die Aufgabe ebenfalls
reichen. Das ganze Programm wird dadurch noch etwas einfacher, weil
dies die Default-Einstellung nach einem Reset ist. Um Prinzip muss man
in der Interruptroutine die acht höchstwertigen Bits des Zählers
in TH0 schreiben und die fünf niedrigstwertigen Bits in die unteren
Bits von TL0. Aber man kann auch TL0 sich selbst überlassen und wie
einen 5-Bit Vorteiler (/32) betrachten. Lässt man außerdem TM0
unverändert auf Null, kommt ein weiterer Vorteiler durch 12 hinzu. Die
nötige Teilerrate für 4 kHz ist dann:
4000 / 12 / 32 = 10
Darin sind gewisse Rundungsfehler enthalten. Genauer gerechnet ergibt
sich eine Frequenz von 16 MHz / 12 / 32 / 10 = 4,167 kHz. Man
könnte es genauer haben, wenn man auch die unteren Bits in TL0
berücksichtigt. Aber sehr viel genauer wird es wegen der Toleranzen des
RC-Taktgenerators nicht mehr. In das TH0-Register wird daher 0xF6 (=
246) geschrieben, damit der Zähler nach zehn vorgeteilten Takten
überläuft. An P10 sollte dann ein Rechtecksignal von 2 kHz erscheinen.
Gemessen wurden 2,09 kHz.
// ADC, UART 9600 und Interrupt 4 kHz, Timer0 Modus 0
#include <N76E003.h>
#include <stdio.h>
void InitUART0_Timer1(long Baudrate);
char getchar(void);
void putchar(char);
void delay(void);
unsigned char p;
void Ex0_ISR (void) __interrupt 0
{
EX0=0;
p++;
p=p & 1;
P10 = p;
delay;
}
void Timer0_ISR (void) __interrupt 1 //interrupt address is 0x000B
{
TH0 = 0xF6;
p++;
p=p & 1;
P10 =
p;
}
void SleepModus(void)
{
EX0=1;
delay:
PCON |= 2; //PD =1 power down
delay;
}
void main(void)
{
unsigned int n;
P0M1=0;P0M2=0;P1M1=128;P1M2=0;P3M1=0;P3M2=0; //P17 Eingang
P30=1;
InitUART0_Timer1(9600);
printf ("UART0, 9600 Baud\r\n");
ADCCON1=1;
//ADC on
ET0 =
1;
//enable Timer1 interrupt
EA =
1;
//enable interrupts
TR0 =
1;
//Timer0 run
while (1)
{
ADCCON0 = 0; //ADC0 = P17
ADCS =
1;
while(ADCF == 0);
n=ADCRH;
if (n<2) SleepModus();
n=16 * n+ ADCRL;
printf ("\r\n ADC = %d",n);
delay();
}
}
void InitUART0_Timer1(long Baudrate) //T1M = 1, SMOD = 1
{
SCON = 0x50; //UART0 Mode1,REN=1,TI=1
TMOD |= 0x20; //Timer1 Mode2
PCON |= 128; //SMOD = 1 UART0 Double Rate Enable
CKCON |=16; //set_T1M
T3CON &=
~64; //BRCK = 0 Serial port 0
baud rate clock source = Timer1
TH1 = 256 -
(1000000/Baudrate+1);
/*16 MHz */
TR1 = 1;
TI = 1; //For printf
}
char getchar(void)
{
char c;
while (!RI);
c = SBUF;
RI = 0;
return (c);
}
void putchar(char c)
{
while (!TI);
TI = 0;
SBUF = c;
}
void delay(void)
{
unsigned int i,j;
for(i=0;i<0xffff;i++)
for(j=0;j<0x000f;j++);
}