Microchip ENC28J60 Ethernet Controller
Allgemeines
Der Integrierte Schaltkreis ENC28J60 ist ein Stand-Alone Ethernet Controller mit SPI Interface. Diese Seite ist eine Kurzfassung mit Erläuterungen des Datenblattes und dient mir als Hilfe bei der Kontrolle der verschiedenen Implementierungen (uIPEnthernet, EthernetENC, FlowCode, ...).
Er bietet folgende Funktionsblöcke
- Physical Layer Block (PHY) mit Anschluss zum Ethernet
- Media Access Control Block (MAC)
- TX/RX Data Handling Block (Filter, FlowControl, DMA, Checksum, Arbiter, ...)
- 8192 Byte SRAM als FIFO organisiert
- SPI zur Kommunikation mit dem uC
Zum Steuern der Blöcke gibt es 2 Registerblöcke
- Control Registers in 4 Bänken organisiert. Jede Bank hat 32 (0x00 - 0x1F) Register, von denen allerdings 6 Common Register (0x1A-0x1F) in jeder Bank gleich sind.
- PHY Registers 0x00 - 0x1F (32) von denen allerdings nur 9 benutzt werden
Control Register
Die Control Register bilden die Hauptschnittstelle bei der Kommunikation zwischen dem Mikrocontroller (o.ä.) und dem Ethernet Controller. Die Register in den 4 Bänken sind nach den Blöcken ETH (E), MAC (MA) und MII (MI) gruppiert, so dass man nicht ständig zwischen den Bänken hin und her schalten muss, wenn man einen Block 'bedient'. Jedes Register beginnt somit entweder mit einem E, MA oder MI.
Common Register
Dies haben eine zentrale Bedeutung, da sie 'immer' erreichbar sind und mit ihnen der Zugriff auf die anderen Register, die auf 4 Bänke aufgeteilt sind steuern.
Es sind:
- Reserved (0x1A)
- EIE (0x1B)
- EIR (0x1C)
- ESTAT ETHERNET CONTROL REGISTER 1 (0x1D)
- ECON2 ETHERNET CONTROL REGISTER 2 (0x1E)
- ECON1
Ethernet Buffer
Der gesamte Buffer 8192 Bytes ist in 2 Teile geteilt. In einen Bereich in den die ankommenden Daten eingeschrieben werden und in einen Teil, der die abgehenden Daten enthält. Die Größe der Bereiche ist programmierbar. Der Eingangspuffer ist als HardwareFIFO organisiert. Dieser FIFO-Bereich wird durch die Register ERXSTH:ERXSTL (Start) and ERXNDH:ERXNDL (End) bestimmt. Die Start- und End-Bytes gehören zum Pufferbereich. Zum Festlegen des FIFO-Puffers muss die Receive Logic (ECON1.RXEN 0) ausgeschaltet sein. Der Pointer (ERXWRPTH:ERXWRPTL readonly) zeigt auf die aktuelle Stelle an die gerade ein empfangenes Byte geschrieben wurde (sucessfully received). Über ihn kann man herausfinden wie viel Platz noch im Empfangspuffer ist. Der Pointer ERXRDPT stellt einen Art Deckel da. Neu ankommende Daten werden nur bis unterhalb dieses Pointer geschrieben und dann verworfen. Das ist i.d.R. nicht erwünscht. Da der Pointer von dem Hostcontroller geschrieben wird kann, das eigentlich nur bedeuten, dass die Daten schneller am PHY ankommen wie sie am SPI abgeholt werden. Der Bereich des Puufers , der nicht als Empfangspuffer definiert ist ist automatisch der Sendepuffer. Gefährlich ist weiterhin, dass die Sendepointer (ETXST, ETXND) nicht auf Kollission mit dem Empfangspuffer geprüft werden. Das Programm im Hostcontroller muss dafür Sorge tragen, dass die Sendepointer nicht im, oder zu nahe am Empfangspuffer liegen. Die LeseSchreibPointer (ERDPT and EWRPT) dien den SPI Kommandos zum Lesen und Schreiben des Puffers.
PHY Register
Die 32 Physical Layer Register sind 16 Bit breit. Es sind allerdings nur 9 davon belegt. Die Register MII (Media Independent Interface Managemant)sind NICHT direkt über das SPI Control Interface erreichbar. Man muss den Umweg über spezielle MAC Register gehen.
Reading
Während der MIIM operation (>10,24 us bis MISTAT.BUSY clear) dürfen keine MIISCAN Operationen durchgeführt werden und auch nicht in das MIWRH geschrieben werden. Ablauf:
- Schreibe die Adresse des gewünschten PHY-Registers in das MIREGADR Register
- Setze das MICMD.MIIRD bit -> es wird darauf hin das MISTAT.BUSY Bit gesetzt
- Warte 10,24 us
- Abfrageschleife bis das MISTAT.BUSY wieder gelöscht wird. Dann ist die Operation abgeschlossen.
- Lösche das MICMD.MIIRD Bit
- Lesen des gewünschten Inhalts aus den MIRDL und MIRDH Register
Writing
Man kann immer nur alle 16 Bit als Ganzes schreiben (MIIM Operation Dauer 10,24 us). Also erst lesen, Bit manipulieren und zurückschreiben. Während der MIIM operation (>10,24 us bis MISTAT.BUSY clear) dürfen keine MIISCAN Operationen durchgeführt werden und auch nicht in das MIWRH geschrieben werden.
- Die gewünschte Adresse in das MIREGADR Register schreiben.
- Die unteren 8 Bit in das MIWRL Register schreiben.
- Die oberen 8 Bit in das MIWRH Register schreiben. Damit starten die Übertragung ins PHY Register automatisch. Das MISTAT.BUSY wird zu 1.
- Sobald die MIIM Operation abgeschlossen ist wird das MISTAT.BUSY Bit gelöscht.
Scanning
Der MAC Block kann so konfiguriert werden, dass er automatische und unmittelbare (back-to-back) Lese-Operationen auf PHY Register ausführt. Dies ist z. B. sehr hilfreich wenn man regelmäßig/ständig Statusinformationen einliest. Während der MISCAN operation (>10,24 us bis MISTAT.BUSY clear) dürfen keine MIIRD Operationen durchgeführt werden und auch nicht in das MIWRH geschrieben werden.
Ablauf:
- Schreibe das gewünschte PHY Register in das MIREGADR Register.
- Setze das MICMD.MIISCAN Bit auf 1. Das MISAT.BUSY wird automatisch gesetzt.
- Warte 10,24 us und abhängig von MSTAT.NVALID
- Wiederhole den Lesevorgang auf MIRDL and MIRDH alle 10,24 us
- Beende SCAN durch MICMD.MIISCAN Bit auf 0 und warte bis das MISTAT.BUSY Bit gelöscht ist.
SPI Instraction Set
Es gibt 7 Befehle mit deren Hilfe der Hostcontroller auf die Control Register und den Puffer zugreifen kann.
- Control Register Read/Write (2)
- Buffer Memory Read/Write (2)
- Bit Field Set/Clear (2)
- Soft Reset
Die Implementierung des SPI behandle ich an dieser Stelle nicht und setze voraus, dass die entsprechende Library wie gewünscht funktioniert.
Implementierung
In dem Teilprogramm "Enc28J60Network.cpp" der uIPEthernet(ENC) Library wird der CS (ChipSelect) Pin mit csPin bezeichnet und mit SS verbunden. Im Programm werden die Adressen der Register auf 8 Bit erweitert.
// ENC28J60 Control Registers // Control register definitions are a combination of address, // bank number, and Ethernet/MAC/PHY indicator bits. // - Register address (bits 0-4) // - Bank number (bits 5-6) // - MAC/PHY indicator (bit 7) #define ADDR_MASK 0x1F #define BANK_MASK 0x60 #define SPRD_MASK 0x80
Weiterhin werden in diesem Teilprogramm, besser als EnC28J60_Base bezeichnet, die Routinen:
- init
- receivePacket
- sendPacket
- readPacket
- writePacket
- copyPacket
- freePacket -> setERXRDPT
- readOp
- writeOp
- readBuffer
- writeBuffer
- setBank
- readReg
- writeReg
- writeRegPair
- phyWrite
- phyRead
- clkout
- getrev
- chksum
- powerOff
- powerOn
- linkStatus
- readByte
- writeByte
- setERXRDPT
- setReadPtr
- blockSize
LinkStatus
- SPI Transaction
- Rückgabewert: Bool
- res = (phyRead(PHSTAT2) & 0x0400) > 0;
phyRead
Dient zum lesen ein PHY Registers. Siehe oben. Ablauf: uint16_t // Zurückgelifert wird ein 16 Bit Wert Enc28J60Network::phyRead(uint8_t address) {
writeReg(MIREGADR,address); // Es wird der 5 Bit Adresswert in das MIREGADR (0x14 Bank 2) Register geschrieben writeReg(MICMD, MICMD_MIIRD); // Es MICMD.MIIRD auf 1 gesetzt MIIRD Media Independent Interface Read Bit im MICMD (0x12 Bank 2) // wait until the PHY read completes while(readReg(MISTAT) & MISTAT_BUSY){ delayMicroseconds(15); // Hier wird immer wieder 15 us gewartet. So habe ich es nicht verstanden. Nicht optimal? delay VOR das while } //and MIRDH // Diesen Kommentar kann ich nicht deuten writeReg(MICMD, 0); return (readReg(MIRDL) | readReg(MIRDH) << 8);
} Soft Reset writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
readReg/writeReg
Die beiden Funktion lösen ein readOp/writeOp nachdem die passende Bank eingestellt ist. Die readReg liefert den 8 Bit Wert eines Control Registers. Es wird die 'passende' Bank (dazu dient die Bankmask Bit 5-6) gesetzt und ein readOp durchgeführt.
readOp/writeOP
Der SoftReset wäre somit eine LeseFunktion. Die Read-Funktion liefert einen 8 Bit Wert. Die passende Bank muss gesetzt sein. Der erste Parameter ist der SPI OP CODE und die Adresse. Im Falle Write kommt noch ein 8 Bit Wert als Data dazu. SPI.transfer ist ein gleichzeitiger Lese-/Schreibzugriff. uint8_t Enc28J60Network::readOp(uint8_t op, uint8_t address) {
CSACTIVE; // issue read command SPI.transfer(op | (address & ADDR_MASK)); // https://www.arduino.cc/en/Reference/SPITransfer // read data if(address & 0x80) { // do dummy read if needed (for mac and mii, see datasheet page 29) SPI.transfer(0x00); } uint8_t c = SPI.transfer(0x00); CSPASSIVE; return c;
}
void Enc28J60Network::writeOp(uint8_t op, uint8_t address, uint8_t data) {
CSACTIVE; // issue write command SPI.transfer(op | (address & ADDR_MASK)); // write data SPI.transfer(data); CSPASSIVE;
}
Software Bibliotheken
EthernetENC
EthernetENC.h greift auf Ethernet.h zu , was wiederum eigentlich UIPEthernet.h ist.
UIPEthernet is a TCP/IP stack that can be used with a enc28j60 based Ethernet-shield. UIPEthernet uses the fine uIP stack by Adam Dunkels <adam@sics.se>
Eigentlich sollte es besser ueberall uIP heiszen.
Die UIPEthernetClass wird mit Ethernet angesprochen und beinhaltet folgende Methoden:
init(CS Pin)
Es wird nur die private Variable csPin gesetzt.
begin (MAC, ....)
5 Varianten: 1 DHCP Variante und 4 Statische Varianten
Wenn nur die IP-Adresse (V4) uebergeben wird, werden dns, gateway zu 1 angenommen und subnet per default auf 255.255.255.0 gesetzt.
maintain()
EthernetLinkStatus linkStatus()
EthernetHardwareStatus hardwareStatus()
IPAddress localIP()
IPAddress subnetMask()
IPAddress gatewayIP()
IPAddress dnsServerIP()
Beispiel: https://github.com/UIPEthernet/UIPEthernet/blob/master/examples/EchoServer/EchoServer.ino
This Hello World example sets up a server at 192.168.1.6 on port 1000. Telnet here to access the service. The uIP stack will also respond to pings to test if you have successfully established a TCP connection to the Arduino.
This example was based upon uIP hello-world by Adam Dunkels <adam@sics.se> Ported to the Arduino IDE by Adam Nielsen <malvineous@shikadi.net> Adaption to Enc28J60 by Norbert Truchsess <norbert.truchsess@t-online.de>
Reduziert auf das Wesentliche ergibt sich folgende Befehlsabfolge: Setup EthernetServer server = EthernetServer(LISTENPORT); Ethernet.begin(mac,myIP,myDNS,myGW,myMASK); server.begin(); // start listening for clients Loop EthernetClient client = server.available() size = client.available() size = client.read(msg,size); client.write(msg,size);