
L'interfaccia SPI
La comunicazione tra ENC28J60 e PIC avviene tramite l'interfaccia SPI; questa supporta soltanto la modalità 0,0 ed il controller è uno slave, quindi è il PIC che fornisce il clock e gestisce la trasmissione.
La massima frequenza ammessa è di 10Mhz per le Rev. B1-B4, mentre è doppia per la B5. Inoltre, a causa di un problema nell'interfaccia (descritto nell'Errata), per le Rev. B1-B4 il clock deve necessariamente essere tra 8 e 10Mhz, oppure il clock del PIC deve essere prelevato dal pin CLKOUT del controller (max 25Mhz). Nel primo caso, dunque, il PIC deve lavorare ad una frequenza tra 32 e 40Mhz.
Comandi SPI
Tramite l'interfaccia è possibile inviare al chip 7 diversi comandi (di 8 bit), eventualmente seguiti da un byte di dati; questi sono:
Nome | 1° byte | 2° byte | Descrizione |
Read Control Register (RCR) | 000 AAAAA | serve a leggere il registro di controllo A | |
Write Control Register (WCR) | 010 AAAAA | DDDDDDDD | per scrivere il byte D all'interno del registro A (nel banco selezionato) |
Read Buffer Memory (RBM) | 00111010 | serve a leggere la memoria RAM del controller all'indirzzo corrente. | |
Write Buffer Memory (WBM) | 01111010 | DDDDDDDD | scrive il byte D all'indirizzo corrente della memoria RAM. |
Bit Field Set (BFS) | 100 AAAAA | DDDDDDDD | setta nel registro A (solo ETH), i bit che in D sono a 1 (OR). |
Bit Field Set (BFS) | 101 AAAAA | DDDDDDDD | resetta nel registro A (solo ETH), i bit che in D sono a 1 (NOT AND). |
System Reset Command (SRC) | 11111111 | resetta il controller. |
Comandi SPI : implementazione
L'inizializzazione del modulo SPI verrà illustrata in seguito, intanto vediamo alcuni metodi di base.
#define spiWrite(x) spiRW(x) #define spiRead() spiRW(0) .... u8 spiRW(u8 data){ SSPBUF = data; while(!PIR1bits.SSPIF); PIR1bits.SSPIF = 0; return SSPBUF; }
Questo è il metodo che ci permette di leggere/scrivere nel bus SPI. Per quanto riguarda la scrittura, il byte da inviare (data) viene messo nel registro SSPBUF (che fa parte del modulo SPI), poi si attende che la trasmissione finisca osservando il bit SSPIF; per leggere un byte, invece, è necessario scrivere un zero nel registro SSPBUF (così vengono generati 8 impulsi di clock), si attende il termine dell'operazione ed il byte letto si troverà nello stesso registro SSPBUF.
In questo caso ho condensato in un unico metodo le operazioni di lettura e scrittura, poi, per chiarezza mentale, ho definito spiWrite e spiRead.
#define CS PORTCbits.RC2 // Chip Select dell'ENC28J60 #define WCR (0b01000000) // Write Control Register command #define BFS (0b10000000) // Bit Field Set command #define BFC (0b10100000) // Bit Field Clear command #define RCR (0b00000000) // Read Control Register command #define RBM (0b00111010) // Read Buffer Memory command #define WBM (0b01111010) // Write Buffer Memory command #define SRC (0b11111111) // System Reset command .... void writeReg(u8 reg, u8 data){ CS = 0; spiWrite(WCR | reg); spiWrite(data); CS = 1; } u8 readMAC(u8 reg){ u8 b; CS = 0; spiWrite(RCR | reg); spiRead(); b = spiRead(); CS = 1; return b; } u8 readETH(u8 reg){ u8 b; CS = 0; spiWrite(RCR | reg); b = spiRead(); CS = 1; return b; }
Con questi tre metodi possiamo leggere e scrivere i registri di controllo (per leggere i registri MII si usa readMAC).
Come si può notare la procedura di lettura di un registro MAC ed ETH è lievemente diversa, in quanto per il primo tipo bisogna inviare un bite zero prima di effettuare la lettura.
void BFCReg(u8 reg, u8 data){ CS = 0; spiWrite(BFC | reg); spiWrite(data); CS = 1; } void BFSReg(u8 reg, u8 data){ CS = 0; spiWrite(BFS | reg); spiWrite(data); CS = 1; } void setBank(u8 bank){ BFCReg(ECON1, 0b11); BFSReg(ECON1, bank); }
Qui vediamo l'implementazione dei comandi Bit Set e Bit Clear; poi il metodo setBank, con il quale possiamo selezionare il banco di memoria dei registri di controllo, agendo sul registro ECON1 (comune a tutti i banchi).
u16 bufSize; .... void encPut(u8 b){ CS = 0; spiWrite(WBM); spiWrite(b); CS = 1; bufSize++; } u8 encGet(){ u8 b; CS = 0; spiWrite(RBM); b = spiRead(); CS = 1; return b; } void sendReset(){ CS = 0; spiWrite(SRC); CS = 1; }
I rimanenti tre comandi sono eseguiti da questi tre metodi: lettura e scrittura della RAM, e reset. La variaible statica bufSize serve
a tener traccia del numero di byte scritti nella RAM (servirà poi).
I registri PHY
I registri PHY sono a 16bit e sono accessibili per mezzo dei registri MII.
Per leggere un registro PHY:
- si mette l'indirizzo nel registro MIREGADR;
- settando il bit MIIRD (1) del registro MICMD, inizia la lettura;
- si attende il termine della lettura osservando il bit BUSY (1) del registro MISTAT;
- si resetta il bit MIIRD;
- il dato sarà presente nei registri MIRDL e MIRDH;
Per scrivere in un registro PHY:
- si pone l'indirizzo del registro in MIREGADR;
- prima si scrive il byte meno significativo in MIWRL, poi scrivendo il byte più significativo in MIWRH inizia la scrittura.
- si attende che il modulo PHY termini l'operazione.
u16 readPHY(u8 reg){ setBank(2); writeReg(MIREGADR, reg); writeReg(MICMD, 0x01); setBank(3); while(readMAC(MISTAT) & 1); setBank(2); writeReg(MICMD, 0x00); return readMAC(MIRDL) | (readMAC(MIRDH) << 8 ); } void writePHY(u8 reg, u16 data){ setBank(2); writeReg(MIREGADR, reg); writeReg(MIWRL, LOW(data)); writeReg(MIWRH, HIGH(data)); setBank(3); while (readMAC(MISTAT) & 1); }
Altri metodi
I seguenti servono a scrivere/leggere più byte dalla RAM del controller; l'indirizzo da cui vengono letti è contenuto nei registri ERDPT, mentre l'indirizzo di scrittura è contenuto nei registri EWRPT (sono autoincrementanti).
void encGetArray(u8* buf, u16 len){ CS = 0; spiWrite(RBM); while(len--) *buf++ = spiRead(); CS = 1; } void encPutArray(u8* buf,u16 len){ bufSize += len; CS = 0; spiWrite(WBM); while(len--) spiWrite(*buf++); CS = 1; } void encPutString(const rom u8 *str){ CS = 0; spiWrite(WBM); while(*str) { spiWrite(*str++); bufSize++; } CS = 1; }
Inizializzazione SPI
L'inizializzazione del modulo SPI avviene con queste due semplici istruzioni:
void encInit(){ TRISB = 0xFF; // configurazione I/O di PORTB TRISC = 0xD1; // configurazione I/O di PORTC PORTC = 0x00; SSPSTAT = 0x40; SSPCON1 = 0x20;
In particolare il modulo MSSP viene abilitato e configurato in modalità "0,0", con clock pari a Fosc/4.
Inizializzazione ENC28J60
L'inizializzazione del controller prevede la configurazione di diversi registri, nonché l'abilitazione alla ricezione.
#define RX_BUF_START 0 #define RX_BUF_END 6499 #define TX_BUF_START 6500 .... setBank(0); writeReg(ERXSTL, LOW(RX_BUF_START)); // writeReg(ERXSTH, HIGH(RX_BUF_START)); // inizio buffer di lettura writeReg(ERXRDPTL, LOW(RX_BUF_END)); // writeReg(ERXRDPTH, HIGH(RX_BUF_END)); // puntatore del buffer di lettura writeReg(ERXNDL, LOW(RX_BUF_END)); // writeReg(ERXNDH, HIGH(RX_BUF_END)); // fine buffer di lettura writeReg(ETXSTL, LOW(TX_BUF_START)); // writeReg(ETXSTH, HIGH(TX_BUF_START)); // inizio buffer di scrittura
Come già detto in precedenza, il buffer dell'ENC28J60 può essere diviso a piacere tra memoria di trasmissione e di ricezione.
Per fare cio si configurano i puntatori del buffer di ricezione; la memoria rimanente sarà il buffer di trasmissione.
I registri ERXST contengono l'indirizzo del primo byte del buffer di ricezione, mentre i registri ERXND l'ultimo byte.
In ERXRDPT, invece, risiede il puntatore di lettura della memoria RX, ovvero contrassegna una zona (insieme a ERXWRPT) che deve ancora essere elaborata dal PIC e quindi non può essere scritta; inizialmente il valore di questo indirizzo è uguale a ERXND (deve essere dispari secondo un problema descritto nell'Errata). Il registro ERXWRPT vale zero al reset ed è aggiornato automaticamente alla ricezione di un pacchetto.
setBank(2); writeReg(MACON1, 0b01101); // MARXEN, TXPAUS, RXPAUS writeReg(MACON3, 0b00110000); // Half Duplex, Padding 60byte, CRC writeReg(MAIPGL, 0x12); // writeReg(MAIPGH, 0x0C); // writeReg(MABBIPG,0x12); // Inter-Packet Gap
Questi registri configurano il modulo MAC. Attraverso il registro MACON1 si abilitano il modulo MAC e la ricezione/trasmissione di trame di pausa.
Con il registro MACON3 si sceglie la modalità Duplex (Half o Full) del modulo MAC che deve essere impostata nello stesso modo anche nel modulo PHY; inoltre in questo registro sono presenti alcune configurazioni sul Padding automatico ed il calcolo del CRC.
I registri MAIPG e MABBIPG contengono i valori delle pause tra pacchetti; quelli presenti nel codice sono i valori standard.
writeReg(MAMXFLL, LOW(1500)); writeReg(MAMXFLH, HIGH(1500));
Nei registri MAXFL (Max Frame Length) viene salvata la massima dimensione consentita per un pacchetto; il controller può essere configurato in modo tale che si rifiuti di inviare un pacchetto che supera questo limite.
#define MY_MAC1 0x00 #define MY_MAC2 0x04 #define MY_MAC3 0xA3 #define MY_MAC4 0x00 #define MY_MAC5 0x00 #define MY_MAC6 0x00 .... setBank(3); writeReg(MAADR1, MY_MAC1); writeReg(MAADR2, MY_MAC2); writeReg(MAADR3, MY_MAC3); writeReg(MAADR4, MY_MAC4); writeReg(MAADR5, MY_MAC5); writeReg(MAADR6, MY_MAC6);
L'indirizzo MAC del nostro dispositivo viene salvato nei registri MACADR; quest'ultimi vengono solamente utilizzati dal filtro per respingere pacchetti non destinati al controller, quindi l'indirizzo non viene inserito automaticamente nei pacchetti da inviare.
writePHY(PHCON2, 0b0000000100000000); // disabilita il loopback writePHY(PHCON1, 0); // abilita il PHY setBank(1); writeReg(ERXFCON, 0b10100001); // imposta i filtri di ricezione BFSReg(ECON1, 0b100); // abilita la ricezione
Con quest'ultimo spezzone termina l'inizializzazione del controller. Il modulo PHY non ha molte opzioni da configurare, le uniche operazioni che vengono effettuate sono la disabilitazione del LoopBack (usato per fare test) e l'abilitazione del modulo.
I filtri sono settati in modo tale da accettare solo pacchetti destinati all'indirizzo MAC configurato e pacchetti di broadcast.
Leggi anche:
Ethernet
Ethernet 2/7
Ethernet 4/7
