SDCC für N76E003
Der
51-kompatible N76E003 von Nuvoton soll nun mit SDCC programmiert
werden. Weil ich keine Kommandozeilen mag, verwende ich Jens'
File Editor als Oberfläche (siehe Labortagebuch: AT89S8252 und SDCC)
. Im ersten Versuch sollte der Chip so behandelt werden, als wäre er
ein stinknormaler 8051. Das kleine Programm soll einfach nur mit den
Leitungen am Port P1 wackeln. Es wird auch ohne Fehlermeldung
kompiliert. Das Ergebnis hat allerdings die Dateiendung ihx statt hex.
Man kann die Datei umbenennen oder aber beim Laden in das Brenner-Tool
alle Dateitypen zulassen. Der Code kommt jedenfalls richtig an.
Auch
der Brennvorgang wird ohne Fehler ausgeführt. Aber dann passiert -
Nichts! Das war ja auch eigentlich zu erwarten, denn man muss dem
Chip erst sagen, welche Eigenschaften der Port haben soll. Im
Datenblatt steht dazu im Abschnitt I/O-Ports, dass dafür zwei Register
pro Port zuständig sind. Und nach einem Reset stehen die Ports im Modus
Eingang.
Daraus ergibt sich, dass man eigentlich nur ein Register auf Null
setzen muss, dann hat man "normale" 51er Ports. Die Adresse findet sich
am schnellsten in der Datei N76E003.h in den Nuvoton-Ordnern zum
Keil-Compiler: sfr P1M1 = 0xB3; Für SDCC muss die Schreibweise
etwas angepasst werden: __sfr __at (0xB3) P1M1;
#include <8051.h>
__sfr __at (0xB3) P1M1;
void delay(void);
void main(void)
{
unsigned char n;
n=0;
P1M1=0;
while(1)
{
n++;
P1 = n;
delay();
}
}
void delay(void)
{
int i,j;
for(i=0;i<0x10;i++)
for(j=0;j<0xff;j++);
}
Damit ist also jetzt das Register P1M1 an der Adresse 0xB3 bekannt und
wird auf Null gesetzt. Das andere Register P1M2 stand ja schon vorher
auf Null. Also übersetzen und brennen: Es blinkt! Und jetzt das Ganze
noch mal für zwei Ports:
#include <8051.h>
__sfr __at (0xB1) P0M1;
__sfr __at (0xB2) P0M2;
__sfr __at (0xB3) P1M1;
__sfr __at (0xB4) P1M2;
void delay(void);
void main(void)
{
unsigned char n;
n=0;
P0M1=0;P0M2=0;P1M1=0;P1M1=0;
while(1)
{
n++;
P0 = n;
P1 = n;
delay();
}
}
void delay(void)
{
int i,j;
for(i=0;i<0x10;i++)
for(j=0;j<0xff;j++);
}
Auch das funktioniert. Nun klappern insgesamt 16 Leitungen. Damit ist
klar, was zu tun ist. Die ganze Datei N76E003.h muss in die
Schreibweise für SDCC übertragen werden. Dann kann diese Datei statt
8051.h eingebunden werden, sodass alle Register des Chips bekannt sind.
Das ist eine reine Fleißarbeit, die vermutlich am Anfang nicht ganz
ohne Fehler gelingen wird. Hier ein Auszug:
/* BYTE Register */
__sfr __at (0x80) P0 ;
__sfr __at (0x81) SP ;
__sfr __at (0x82) DPL ;
__sfr __at (0x83) DPH ;
__sfr __at (0x84) RCTRIM0 ;
__sfr __at (0x85) RCTRIM1 ;
__sfr __at (0x86) RWK ;
__sfr __at (0x87) PCON ;
__sfr __at (0x88) TCON ;
__sfr __at (0x89) TMOD ;
__sfr __at (0x8A) TL0 ;
__sfr __at (0x8B) TL1 ;
__sfr __at (0x8C) TH0 ;
__sfr __at (0x8D) TH1 ;
__sfr __at (0x8E) CKCON ;
__sfr __at (0x8F) WKCON ;
__sfr __at (0x90) P1 ;
__sfr __at (0x91) SFRS ; //TA Protection
__sfr __at (0x92) CAPCON0 ;
__sfr __at (0x93) CAPCON1 ;
__sfr __at (0x94) CAPCON2 ;
__sfr __at (0x95) CKDIV ;
__sfr __at (0x96) CKSWT ; //TA Protection
__sfr __at (0x97) CKEN ; //TA Protection
__sfr __at (0x98) SCON ;
__sfr __at (0x99) SBUF ;
__sfr __at (0x9A) SBUF_1 ;
__sfr __at (0x9B) EIE ;
__sfr __at (0x9C) EIE1 ;
__sfr __at (0x9F) CHPCON ; //TA Protection
... usw ...
Download (update 19.9.19): N76E003.zip
Nun sieht das erste Beispielprogramm so aus:
#include <N76E003.h>
void delay(void);
void main(void)
{
unsigned char n;
n=0;
P0M1=0;P0M2=0;P1M1=0;P1M1=0;
while(1)
{
n++;
P0 = n;
P1 = n;
delay();
}
}
Und blinkt, und blinkt, und blinkt ...
Taktumschaltung
Weil nun SDCC alle Register des Controllers kennt, kann ich mich auch
an andere Dinge wagen. Mich interessieren z.B. die Möglichkeiten des
Taktgenerators. Normalerweise läuft der interne Taktoszillator mit 16
MHz. Der Clock-Teiler CKDIV steht dann auf Null. Aber es ist jedes
gerade Teilerverhältnis bis 510 möglich. Andere Controller erlauben nur
Zweierpotenzen, hier hat man ganz andere Möglichkeiten. Zum Test habe
ich CKDIC = 160 probiert. Die Taktfreqeunze beträgt dann 50 kHz. Den
aktuellen Takt kann man auf den Pin P1.1 schalten und dort mit dem Oszi
oder einem Frequenzzähler begutachten.
void main(void)
{
P0M1=0;P0M2=0;P1M1=0;P1M1=0;
// CKDIV= 160; 16 MHz /160/2 = 50 kHz
TA=0xAA; // Timed access
TA=0x55;
CKSWT = 4;// 10 kHz oscillator
CKDIV= 250;// 10 kHz /250/2 = 20 Hz
CKCON |=2; //Clock out at P1.1
P1=255;
while(1);
}
50 kHz ist schon sehr langsam und stromsparend. Aber es gibt auch noch
den 10-kHz-RC-Oszilaltor. Er wird über das Register CKSWT
ausgewählt, das TA-protected ist. Das bedeutet, man kann nicht einfach
so darauf zugreifen, sondern muss sich vorher einen Timed Access
freischalten. Dazu braucht es zwei ganz bestimmte Zugriffe auf das
TA-Register, erst 0xAA und dann 0x55. Das soll fatale Fehler
verhindern. Wenn ich z.B. eine bemannte Flugtaxi-Drohne damit steuere
und dann durch einen Programmfehler auf 10 kHz runtertakte, wird die
Steuerung zu langsam, und die Drohne stürzt ab. Wenn ich allerdings
fehlerbedingt den Takt durch 160 teile, geht das zwar ohne TA, führt
aber dennoch zum Absturz. Man sieht. Flugsicherheit ist relativ. Ich
bleibe deshalb lieber am Boden.
10 kHz geht und konnte am Pin P1.1 gemessen werden. Aber funktioniert
dann auch noch der Clock-Teiler? Tut er! Und deshalb läuft jetzt hier
die vermutlich niedrigste Taktrate aller Zeiten und Controller. Der
Controller arbeitet mit einem Takt von 20 Hz! Ich weiß zwar noch nicht,
ob es jemals eine sinnvolle Anwendung dafür geben wird, aber vorerst
ist am Clock-Ausgang P1.1 eine LED angeschlossen, die nun gut sichtbar
vor sich hin flackert.
weiter ...