N76E003 Timer und Interupt   


Elektronik-Labor  Projekte  Nuvoton 



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++);

}




Elektronik-Labor  Projekte   Nuvoton