>> Atmel Webserver mit ENC28J60

Geräte über das Internet zu steuern, war schon seit langem eine fixe Idee von mir. Doch leider gab es da etliche Hürden zu nehmen, ich hatte z.B. keine Ahnung über die Interna des Internet Protokolls und die bis dahin erhältlichen Ethernetcontroller (meistens Realtek RTL 8019 oder Crystal CS 8900) waren auch nicht gerade kompakt und einfach programmierbar, da sie ursprünglich für PC-Bussysteme entwickelt worden sind.

ENC28J60 Embedded Ethernetcontroller

Das änderte sich schlagartig als Microchip den ENC28J60 ankündigte, einen sehr kompakten Ethernetcontroller der mit sehr wenig externen Bauteilen auskommt. Dass der ENC speziell für Embedded-Anwendungen entworfen wurde, erkennt man an der SPI-Schnittstelle , welche eine Anbindung an Microcontroller auch mit wenigen IO-pins erlaubt.

Hier einige Features des ENC28J60:

ENC28J60 Datasheet (c) by Microchip Technology

Im obigen Blockschaltbild kann man die prinzipelle Anbindung an einen Microcontroller sehen. Über /CS wird der ENC selektiert, SI (serieller Dateneingang) und SO (Serieller Datenausgang) stellen zusammen mit SCK (Takt) eine Synchrone Serielle Schnittstelle dar über die Daten von- oder zum ENC transferriert werden. Über /INT kann der ENC dem Mikrocontroller anzeigen, dass z.B. ein passendes Ethernetpaket eingetroffen ist.
Zur Anbindung des ENCs ans Ethernet wird noch ein geeigneter Übertrager (Transformer) benötigt. In diesem Projekt wird der MagJack(TM) verwendet, eine RJ45-Netzwerkbuchse mit eingebautem Übertrager und zwei frei ansteuerbaren LEDs, die z.B. den Link und Transmitstatus anzeigen können.

Speicherorganisation des ENC28J60

Der ENC besitzt 128 Kontroll-Register, die in 4 Bänke unterteilt sind. Die aktive Bank lässt sich mit dem Register ECON1 setzen.  ECON1 ist ein sog. "Common-Register" genau wie die Register ECON2,ESTAT,EIR,EIE. Diese Register besitzen eine feste Adresse unabhängig von der selektierten Bank. ECON1 hat zum Beispiel immer die Adresse 0x1f, egal welche Bank gerade selektiert ist. Die ersten vier Register von Bank 0  stellen einen Lese- und Schreibpointer dar, welche auf eine Adresse im 8 kb großen Ethernet Puffer zeigen. Auf diese Adresse kann man dann mit dem Read-/WriteBufferMemory-Befehl zugreifen (RBM/WBM siehe unten). Die PHY-Register zur Konfiguration des Physical Layers (PHY) stellen eine Ausnahme dar, da sie nur indirekt über spezielle Kontrollregister gelesen und geschrieben werden können, doch dazu später mehr. 

Programmierung des ENC28J60

Die untenstehende Tabelle aus dem Microchip Datenblatt zeigt die möglichen Befehle und Adressierungsmodi des ENC. Hierbei sind grundsätzlich zwei Arten von Befehlen zu unterscheiden. Die einen greifen auf die Register des ENC28J60 zu (z.B. RCR/ WCR), mit den anderen kann man auf den Ethernetpuffer (siehe oben) des ENC zugreifen (mit RBM/WBM).

ENC28J60 Datasheet (c) by Microchip Technology

Die Befehle setzen sich Prinzipell aus einem 3bit Opcode (Operation Code) zusammen und einem 5bit breitem Argument. Das Argument ist entweder eine feste Konstante, wie z.B. beim Resetbefehl (SRC) oder das Argument gibt die Adresse eins Registers an. Bedingt durch die wortbreite von 5 bit kann eine Registeradresse also einen Wert von 0-31 annehmen. Den schreibenden Befehlen wie WCR, WBM, BFS und BFC folgen dann ein oder mehrere Datenbytes über die SPI-Schnittstelle.

Bei den Lesenden Befehlen wie RCR und RBM ist es im Prinzip genauso, nur dass nach absetzten des Lese-Befehls die Datenrichtung gändert wird und somit ein oder mehrere Bytes vom Microcontroller gelesen werden.

  

Im Sourcecode sieht das ganze wie folgt aus:

// macros
#define enc_start() ENC_CSPORT &= ~ENC_CSPIN
#define enc_stop() ENC_CSPORT |= ENC_CSPIN

/*
 * enc_reset()
 *
 **********************************************************/

void enc_reset(void){
 enc_start();

 SPDR = ENCMD_SC;
 while(!(SPSR & (1 << SPIF)));
 
 enc_stop();
}

/*
 * enc_bfs()
 *
 **********************************************************/

void enc_bfs(u08 reg,u08 data){
 enc_start();

 SPDR = ENCMD_BFS|reg;
 while(!(SPSR & (1 << SPIF)));
 
 SPDR = data;
 while(!(SPSR & (1 << SPIF)));
 
 enc_stop();
}

/*
 * enc_bfc()
 *
 **********************************************************/

void enc_bfc(u08 reg,u08 data){
 enc_start();

 SPDR = ENCMD_BFC|reg;
 while(!(SPSR & (1 << SPIF)));
 
 SPDR = data;
 while(!(SPSR & (1 << SPIF)));
 
 enc_stop();
}

/*
 * enc_wcr()
 *
 **********************************************************/

void enc_wcr(u08 reg,u08 data){
 enc_start();

 SPDR = ENCMD_WCR|reg;
 while(!(SPSR & (1 << SPIF)));
 
 SPDR = data;
 while(!(SPSR & (1 << SPIF)));
 
 enc_stop();
}

/*
 * enc_wbm()
 *
 **********************************************************/

void enc_wbm(u08 *data,int len){
int i;

 enc_start();

 SPDR = ENCMD_WBM;
 while(!(SPSR & (1 << SPIF)));
 
for(i=0; i < len ;i++)
 { 
  SPDR = *data++;
  while(!(SPSR & (1 << SPIF)));
 }
  
 enc_stop();
}


/*
 * enc_rbm()
 *
 **********************************************************/

void enc_rbm(u08 *data,int len){
int i;

 enc_start();

 SPDR = ENCMD_RBM;
 while(!(SPSR & (1 << SPIF)));
 
for(i=0; i < len ;i++)
 {
  SPDR = 0; while(!(SPSR & (1 << SPIF)));
  *data++ = SPDR;
 }
  
 enc_stop();
}


/*
 * enc_rcr()
 *
 **********************************************************/

u08 enc_rcr(u08 reg,u08 method){
 enc_start();

 SPDR = ENCMD_RCR|reg;
 while(!(SPSR & (1 << SPIF)));

 SPDR = 0;
 while(!(SPSR & (1 << SPIF)));
 
 if(method) // zum Lesen von PHY-Registern
 {
  SPDR = 0;
  while(!(SPSR & (1 << SPIF)));
 }

 enc_stop();
 return(SPDR);
}


/*
 * enc_banksel()
 *
 **********************************************************/

void enc_banksel(u08 bank){
static u08 lastbank=0xf;

 if(bank!=lastbank) // bank switching needed?
 {
  enc_bfc(ECON1,ECON1_BSEL0|ECON1_BSEL1);
  enc_bfs(ECON1,bank);
  lastbank=bank;
 }
}

Initialisierungssequenz des ENC28J60

Bevor der ENC28J60 zum Empfangen und Senden von Ethernetpaketen benutzt werden kann, muss dieser erst einmal entsprechend konfiguriert werden. Microchip beschreibt im Datenblatt eine Initialisierungssequenz, die wie folgt ausssieht:

  1. Reset
    Kann entweder durch die Leitung /RESET oder durch senden des SRC-Kommandos erfolgen (SystemResetCommand)
  2. Empfangs-/Sendepuffer definieren
    Mit den Registern ERXST (RecieveBufferStart) und ERXND (RecieveBufferEnd) wird im Ethernetpuffer ein Teil des Speichers als Empfangspuffer definiert. Vom ENC empfangene Pakete, die zuvor den MAC-Filter passiert haben, werden hier abgelegt.
    Der übrige Speicher, welcher nicht als Empfangspuffer definiert wurde ist automatisch Sendepuffer oder kann für irgendwelche Zwecke als Zwischen-Speicher benutzt werden.
  3. Empfangsfilter
    Durch das Register ERXFCON lassen sich die Empfangsfilter konfigurieren. Hier kann man angeben welche Pakete der ENC empfangen oder verwerfen soll. Beispielsweise kann man hier einstellen, daß alle Pakete mit Broadcastast MAC-Adresse verworfen werden. In userem Fall wird hier der Wert 0x00 angegeben. Bei diesem Wert werden alle Pakete empfangen und somit an den TCP/IP-Stack weitergeleitet
  4. Auf OST warten
    Bei einem "Kaltstart" des ENCs muss jetzt erst auf die Beendigung des Oscillation Startup Timers gewartet werden, bevor man MAC und PHY Register schreiben kann. Dies geschieht indem man im ESTAT Register auf das setzen des CLKRDY-Bits wartet.
  5. MAC Initialisierung
    Hier werden Parameter wie automatische CRC-Generierung, Duplexart oder die max. Länge der vom ENC akzeptierten Ethernetpaktete angegeben, genaueres bitte im Datenblatt nachlesen
  6. PHY Initialisierung
    Hier gibts so lustige Sachen einzustellen, wie zu zum Beispiel das JABBER-Bit ;-) Von den Sachen sollte man aber die Finger davon lassen. Wichtig ist die Aktivierung des Twisted-Pair Interfaces durch das HDLDIS-Bit im PHCON2-Register und vielleicht noch eine Konfiguration der Status-LEDs im PHLCON-Register

 Daten empfangen

Nach der Initialisierung wird der ENC durch setzen des RXEN Bits im ECON1 Register empfangsbereit geschaltet. Wenn ein an ihn adressiertes Ethernetpaket empangen wurde wird es erstmal automatisch in den Ethernetpuffer geschrieben und das PacketCount-Register (EPKTCNT) um eins erhöht. Es ist somit also möglich mehrere Pakete auf einmal zu empfangen und nach einander abzuarbeiten.

Der Microcontroller fragt also ab ob EPKTCNT > 0 ist, also ob ein oder mehrere Datenpakete empfangen worden sind. Sind Daten zum lesen vorhanden, liest er erst mal 6 Byte aus dem Puffer. Bei den ersten 2 Byte handelt es sich um den sog. "Next Paket Pointer". Diese 16 Bit Adresse zeigt auf das nächste empfange Paket im Ethernetpuffer (die Pakte werden also als einfach verkettete Liste im Puffer abgelegt). Die weiteren 4 Byte werden als Statusvector bezeichnet und enthalten Informationen über das empfangene Paket, wie z.B. die Anzahl der empfangenen Bytes oder bestimmte Fehlermeldungen.


(c) 2007 by Ulrich Esser