Através da Interface é possivel enviar para o chip sete comandos.
Nome 1Byte 2Byte
Read control Register (RCR) 000AAAAA
Read Buffer Memory (RBM) 00111010
Write Control Register (WCR) 010AAAAA DDDDDDDD
Write Buffer Memory (WBM) 01111010 DDDDDDDD
Bit Field Set (BFS) 100AAAAA DDDDDDDD
Bit Field Clear (BFC) 101AAAAA DDDDDDDD
System Command (Soft Reset) (SC) 11111111
#include "enc28j60.h"
Com estas duas funções abaixo, você pode trabalhar com os operadores de leitura e escrita nas memórias Register Control e Ethernet Buffer do ENC. Adicione no arquivo ENC28J60.h
#define CS P1_4 //habilita o ENC28J60
BYTE enc28j60LerOp(BYTE op, BYTE address)
{
BYTE dado;
CS=FALSE; //habilito ENC28J60
SPI_WRITE(op | (address & ADDR_MASK));
//escrevo na SPI operação e endereço
dado=SPI_READ(); //dados vazios
if(address & 0x80)
dado=SPI_READ(); //dados em SPDR
CS=TRUE; //desabilito ENC28J60
return(dado);
}
void enc28j60EscreveOp(BYTE op, BYTE address, BYTE dado)
{
CS=FALSE; //habilito ENC28J60
SPI_WRITE(op | (address & ADDR_MASK));
//escrevo operação e endereço
SPI_WRITE(dado); //escrevo dado
CS=TRUE; //desabilito ENC28J60
}
Control Register do ENC é dividido em quatro banco de registradores e para selecionarmos um banco , precisamos endereçar com os bits BSEL1 e BSEL0 que estão no ECON1.(ver página 12 do datasheet). Outra função para ENC28J60.h.
void enc28j60SetBanco(BYTE address)
{
// set o banco se precisar
if((address & BANK_MASK) != Enc28j60Bank)
{
//primeiro limpo com o comando BFC
enc28j60EscreveOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
//agora seto com o comando BFS
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
//salvo banco setado
Enc28j60Bank = (address & BANK_MASK);
}
}
Aqui eu escrevo e leio control register.
BYTE enc28j60Ler(BYTE address)
{
// seleciono o banco para ler
enc28j60SetBanco(address);
// faço a leitura
return enc28j60LerOp(ENC28J60_READ_CTRL_REG, address);
}
void enc28j60Escreve(BYTE address, BYTE dado)
{
// seleciono o banco para escrever
enc28j60SetBanco(address);
// faço a escrita
enc28j60EscreveOp(ENC28J60_WRITE_CTRL_REG, address, dado);
}
Os registradores PHY não são diretamente assesiveis pela SPI, devemos acessa-los através do registrador MIREGADR, que está no Register Control, veja abaixo as funções de leitura e escrita.Algumas coisas estão explicadas outras ainda não entedi.
WORD enc28j60_Ler_phyreg(BYTE address)
{
WORD dado;
//set o endereço do registrador PHY
enc28j60Escreve(MIREGADR, address);
//set o MICMD_MIIRD
enc28j60Escreve(MICMD, MICMD_MIIRD);
// Aguardo...
while( (enc28j60Ler(MISTAT) & MISTAT_BUSY) );
// parar
enc28j60Escreve(MICMD, MICMD_MIIRD);
// dado 16 bits
dado = enc28j60Ler ( MIRDL );
dado |= enc28j60Ler ( MIRDH );
return dado;
}
void enc28j60PhyEscreve(BYTE address,WORD dado)
{
//set o endereço do registrador PHY
enc28j60Escreve(MIREGADR, address);
// escrevo o dado no registrador
enc28j60Escreve(MIWRL,dado);
enc28j60Escreve(MIWRH,dado>>8);
// Aguardo...
while(enc28j60Ler(MISTAT) & MISTAT_BUSY)
{
tempo();
}
}
Esta é a função de inicialização do ENC onde eu configuro filtros, tamanho de pacote, camada física, MAC e outras coisas, aconselho dar uma olhada no data sheet para poder entender bem está função.Algumas coisas eu entendi outras não.
void enc28j60_init(void)
{
WORD dado;
RESET = FALSE;
timer(10);
RESET = TRUE;
timer(10);
timer(200);
CS=FALSE;
//reset o sistema
enc28j60EscreveOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
timer(50);
//limpo variavel
next_packet_ptr = RXSTART_INIT;
// Rx inicio
enc28j60Escreve(ERXSTL, RXSTART_INIT&0xFF);
enc28j60Escreve(ERXSTH, RXSTART_INIT>>8);
// set endereço de ponteiro
enc28j60Escreve(ERXRDPTL, RXSTART_INIT&0xFF);
enc28j60Escreve(ERXRDPTH, RXSTART_INIT>>8);
// RX fim
enc28j60Escreve(ERXNDL, RXSTOP_INIT&0xFF);
enc28j60Escreve(ERXNDH, RXSTOP_INIT>>8);
// TX inicio
enc28j60Escreve(ETXSTL, TXSTART_INIT&0xFF);
enc28j60Escreve(ETXSTH, TXSTART_INIT>>8);
// TX fim
enc28j60Escreve(ETXNDL, TXSTOP_INIT&0xFF);
enc28j60Escreve(ETXNDH, TXSTOP_INIT>>8);
// habilito recebimento MAC
// habilito full duplex
enc28j60Escreve(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
// habilito enchimento automatico de 60 bytes e operações CRC
enc28j60Escreve(MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
//aguarda transmisão se o mio estiver ocupado
enc28j60Escreve(MACON4, MACON4_DEFER);
// Colisões ocorrem menos freqüentemente com um número maior.
enc28j60Escreve(MACLCON2, 63);
//configuração padrão de muitas aplicações. ver datasheet pagina 24
enc28j60Escreve(MAIPGL, 0x12);
enc28j60Escreve(MAIPGH, 0x0C);
//set o máximo comprimento do frame 1518
enc28j60Escreve(MAMXFLL, MAX_FRAMELEN&0xFF);
enc28j60Escreve(MAMXFLH, MAX_FRAMELEN>>8);
//set o MAC
enc28j60Escreve(MAADR5, Ler_Ram(MY_MAC+0));
enc28j60Escreve(MAADR4, Ler_Ram(MY_MAC+1));
enc28j60Escreve(MAADR3, Ler_Ram(MY_MAC+2));
enc28j60Escreve(MAADR2, Ler_Ram(MY_MAC+3));
enc28j60Escreve(MAADR1, Ler_Ram(MY_MAC+4));
enc28j60Escreve(MAADR0, Ler_Ram(MY_MAC+5));
dado = PHCON2_HDLDIS;
enc28j60PhyEscreve(PHCON2, dado);
//configurações dos leds (ver página 9)
dado = 0x0472;
enc28j60PhyEscreve(PHLCON,dado);
//habilito CRC UCEN e PMEN
enc28j60Escreve(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
enc28j60Escreve(EPMM0, 0x3f);
enc28j60Escreve(EPMM1, 0x30);
enc28j60Escreve(EPMCSL, 0xf9);
enc28j60Escreve(EPMCSH, 0xf7);
enc28j60Escreve(MABBIPG, 0x12);
enc28j60SetBanco(ECON1);
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
timer(50);
}
Com estas duas funções nós conseguimos enviar e receber um pacote pela ethernet.
void enc28j60_envia_pacote (WORD comprimento )
{
WORD endereco_ram = 0; //primeiro endereço da ram
//set o ponteiro de inicio na area de transmição
enc28j60Escreve(EWRPTL, TXSTART_INIT );
enc28j60Escreve(EWRPTH, TXSTART_INIT>>8);
//Set o ponteiro TXND corresponde o tamanho do pacote recebido
enc28j60Escreve(ETXNDL, (TXSTART_INIT+comprimento));
enc28j60Escreve(ETXNDH, (TXSTART_INIT+comprimento)>>8);
enc28j60EscreveOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
CS=FALSE;
//comando de escrita de memoria
SPI_WRITE(ENC28J60_WRITE_BUF_MEM);
while(comprimento)
{
comprimento--;
SPI_WRITE(Ler_Ram(endereco_ram++));
}
CS=TRUE;
//vamos transmitir
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
// Reset se houver problema na transmissão
if( (enc28j60Ler(EIR) & EIR_TXERIF) )
{
enc28j60EscreveOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
}
}
WORD enc28j60_ler_pacote (WORD max_comprimento )
{
WORD endereco_ram=0; //primeiro endereço da RAM externa
WORD rx_status;
WORD dado_comprimento;
if( enc28j60Ler(EPKTCNT) == 0 )
{
return 0;
}
// Set o ponteiro de leitura para inicializar o recebimento
enc28j60Escreve(ERDPTL, (next_packet_ptr));
enc28j60Escreve(ERDPTH, (next_packet_ptr)>>8);
next_packet_ptr = enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0);
next_packet_ptr |= enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0)<<8;
// comprimento do dado
dado_comprimento = enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0);
dado_comprimento |= enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0)<<8;
// status
rx_status = enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0);
rx_status |= enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0)<<8;
if ( dado_comprimento > (max_comprimento-1) )
{
dado_comprimento = max_comprimento-1;
}
if ( (rx_status & 0x80)==0 )
{
dado_comprimento = 0;
}
else
{
rx_status = dado_comprimento;
CS=0;
//salvo pacote na ram externa
SPI_WRITE(ENC28J60_READ_BUF_MEM);
while(rx_status)
{
rx_status--;
Escrita_Ram(endereco_ram++,SPI_READ());
}
CS=1;
}
//mudo o ponteiro para o recebimento do proximo pacote
enc28j60Escreve(ERXRDPTL, (next_packet_ptr));
enc28j60Escreve(ERXRDPTH, (next_packet_ptr)>>8);
// decremento
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
return dado_comprimento;
}
Finalizamos o arquivo ENC28J60.h na próxima postagem nós vamos enviar dados simples para testarmos as funções.
#include "enc28j60.h"
Com estas duas funções abaixo, você pode trabalhar com os operadores de leitura e escrita nas memórias Register Control e Ethernet Buffer do ENC. Adicione no arquivo ENC28J60.h
#define CS P1_4 //habilita o ENC28J60
BYTE enc28j60LerOp(BYTE op, BYTE address)
{
BYTE dado;
CS=FALSE; //habilito ENC28J60
SPI_WRITE(op | (address & ADDR_MASK));
//escrevo na SPI operação e endereço
dado=SPI_READ(); //dados vazios
if(address & 0x80)
dado=SPI_READ(); //dados em SPDR
CS=TRUE; //desabilito ENC28J60
return(dado);
}
void enc28j60EscreveOp(BYTE op, BYTE address, BYTE dado)
{
CS=FALSE; //habilito ENC28J60
SPI_WRITE(op | (address & ADDR_MASK));
//escrevo operação e endereço
SPI_WRITE(dado); //escrevo dado
CS=TRUE; //desabilito ENC28J60
}
Control Register do ENC é dividido em quatro banco de registradores e para selecionarmos um banco , precisamos endereçar com os bits BSEL1 e BSEL0 que estão no ECON1.(ver página 12 do datasheet). Outra função para ENC28J60.h.
void enc28j60SetBanco(BYTE address)
{
// set o banco se precisar
if((address & BANK_MASK) != Enc28j60Bank)
{
//primeiro limpo com o comando BFC
enc28j60EscreveOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
//agora seto com o comando BFS
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
//salvo banco setado
Enc28j60Bank = (address & BANK_MASK);
}
}
Aqui eu escrevo e leio control register.
BYTE enc28j60Ler(BYTE address)
{
// seleciono o banco para ler
enc28j60SetBanco(address);
// faço a leitura
return enc28j60LerOp(ENC28J60_READ_CTRL_REG, address);
}
void enc28j60Escreve(BYTE address, BYTE dado)
{
// seleciono o banco para escrever
enc28j60SetBanco(address);
// faço a escrita
enc28j60EscreveOp(ENC28J60_WRITE_CTRL_REG, address, dado);
}
Os registradores PHY não são diretamente assesiveis pela SPI, devemos acessa-los através do registrador MIREGADR, que está no Register Control, veja abaixo as funções de leitura e escrita.Algumas coisas estão explicadas outras ainda não entedi.
WORD enc28j60_Ler_phyreg(BYTE address)
{
WORD dado;
//set o endereço do registrador PHY
enc28j60Escreve(MIREGADR, address);
//set o MICMD_MIIRD
enc28j60Escreve(MICMD, MICMD_MIIRD);
// Aguardo...
while( (enc28j60Ler(MISTAT) & MISTAT_BUSY) );
// parar
enc28j60Escreve(MICMD, MICMD_MIIRD);
// dado 16 bits
dado = enc28j60Ler ( MIRDL );
dado |= enc28j60Ler ( MIRDH );
return dado;
}
void enc28j60PhyEscreve(BYTE address,WORD dado)
{
//set o endereço do registrador PHY
enc28j60Escreve(MIREGADR, address);
// escrevo o dado no registrador
enc28j60Escreve(MIWRL,dado);
enc28j60Escreve(MIWRH,dado>>8);
// Aguardo...
while(enc28j60Ler(MISTAT) & MISTAT_BUSY)
{
tempo();
}
}
Esta é a função de inicialização do ENC onde eu configuro filtros, tamanho de pacote, camada física, MAC e outras coisas, aconselho dar uma olhada no data sheet para poder entender bem está função.Algumas coisas eu entendi outras não.
void enc28j60_init(void)
{
WORD dado;
RESET = FALSE;
timer(10);
RESET = TRUE;
timer(10);
timer(200);
CS=FALSE;
//reset o sistema
enc28j60EscreveOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
timer(50);
//limpo variavel
next_packet_ptr = RXSTART_INIT;
// Rx inicio
enc28j60Escreve(ERXSTL, RXSTART_INIT&0xFF);
enc28j60Escreve(ERXSTH, RXSTART_INIT>>8);
// set endereço de ponteiro
enc28j60Escreve(ERXRDPTL, RXSTART_INIT&0xFF);
enc28j60Escreve(ERXRDPTH, RXSTART_INIT>>8);
// RX fim
enc28j60Escreve(ERXNDL, RXSTOP_INIT&0xFF);
enc28j60Escreve(ERXNDH, RXSTOP_INIT>>8);
// TX inicio
enc28j60Escreve(ETXSTL, TXSTART_INIT&0xFF);
enc28j60Escreve(ETXSTH, TXSTART_INIT>>8);
// TX fim
enc28j60Escreve(ETXNDL, TXSTOP_INIT&0xFF);
enc28j60Escreve(ETXNDH, TXSTOP_INIT>>8);
// habilito recebimento MAC
// habilito full duplex
enc28j60Escreve(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
// habilito enchimento automatico de 60 bytes e operações CRC
enc28j60Escreve(MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN);
//aguarda transmisão se o mio estiver ocupado
enc28j60Escreve(MACON4, MACON4_DEFER);
// Colisões ocorrem menos freqüentemente com um número maior.
enc28j60Escreve(MACLCON2, 63);
//configuração padrão de muitas aplicações. ver datasheet pagina 24
enc28j60Escreve(MAIPGL, 0x12);
enc28j60Escreve(MAIPGH, 0x0C);
//set o máximo comprimento do frame 1518
enc28j60Escreve(MAMXFLL, MAX_FRAMELEN&0xFF);
enc28j60Escreve(MAMXFLH, MAX_FRAMELEN>>8);
//set o MAC
enc28j60Escreve(MAADR5, Ler_Ram(MY_MAC+0));
enc28j60Escreve(MAADR4, Ler_Ram(MY_MAC+1));
enc28j60Escreve(MAADR3, Ler_Ram(MY_MAC+2));
enc28j60Escreve(MAADR2, Ler_Ram(MY_MAC+3));
enc28j60Escreve(MAADR1, Ler_Ram(MY_MAC+4));
enc28j60Escreve(MAADR0, Ler_Ram(MY_MAC+5));
dado = PHCON2_HDLDIS;
enc28j60PhyEscreve(PHCON2, dado);
//configurações dos leds (ver página 9)
dado = 0x0472;
enc28j60PhyEscreve(PHLCON,dado);
//habilito CRC UCEN e PMEN
enc28j60Escreve(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
enc28j60Escreve(EPMM0, 0x3f);
enc28j60Escreve(EPMM1, 0x30);
enc28j60Escreve(EPMCSL, 0xf9);
enc28j60Escreve(EPMCSH, 0xf7);
enc28j60Escreve(MABBIPG, 0x12);
enc28j60SetBanco(ECON1);
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
timer(50);
}
Com estas duas funções nós conseguimos enviar e receber um pacote pela ethernet.
void enc28j60_envia_pacote (WORD comprimento )
{
WORD endereco_ram = 0; //primeiro endereço da ram
//set o ponteiro de inicio na area de transmição
enc28j60Escreve(EWRPTL, TXSTART_INIT );
enc28j60Escreve(EWRPTH, TXSTART_INIT>>8);
//Set o ponteiro TXND corresponde o tamanho do pacote recebido
enc28j60Escreve(ETXNDL, (TXSTART_INIT+comprimento));
enc28j60Escreve(ETXNDH, (TXSTART_INIT+comprimento)>>8);
enc28j60EscreveOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
CS=FALSE;
//comando de escrita de memoria
SPI_WRITE(ENC28J60_WRITE_BUF_MEM);
while(comprimento)
{
comprimento--;
SPI_WRITE(Ler_Ram(endereco_ram++));
}
CS=TRUE;
//vamos transmitir
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
// Reset se houver problema na transmissão
if( (enc28j60Ler(EIR) & EIR_TXERIF) )
{
enc28j60EscreveOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
}
}
WORD enc28j60_ler_pacote (WORD max_comprimento )
{
WORD endereco_ram=0; //primeiro endereço da RAM externa
WORD rx_status;
WORD dado_comprimento;
if( enc28j60Ler(EPKTCNT) == 0 )
{
return 0;
}
// Set o ponteiro de leitura para inicializar o recebimento
enc28j60Escreve(ERDPTL, (next_packet_ptr));
enc28j60Escreve(ERDPTH, (next_packet_ptr)>>8);
next_packet_ptr = enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0);
next_packet_ptr |= enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0)<<8;
// comprimento do dado
dado_comprimento = enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0);
dado_comprimento |= enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0)<<8;
// status
rx_status = enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0);
rx_status |= enc28j60LerOp(ENC28J60_READ_BUF_MEM, 0)<<8;
if ( dado_comprimento > (max_comprimento-1) )
{
dado_comprimento = max_comprimento-1;
}
if ( (rx_status & 0x80)==0 )
{
dado_comprimento = 0;
}
else
{
rx_status = dado_comprimento;
CS=0;
//salvo pacote na ram externa
SPI_WRITE(ENC28J60_READ_BUF_MEM);
while(rx_status)
{
rx_status--;
Escrita_Ram(endereco_ram++,SPI_READ());
}
CS=1;
}
//mudo o ponteiro para o recebimento do proximo pacote
enc28j60Escreve(ERXRDPTL, (next_packet_ptr));
enc28j60Escreve(ERXRDPTH, (next_packet_ptr)>>8);
// decremento
enc28j60EscreveOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
return dado_comprimento;
}
Finalizamos o arquivo ENC28J60.h na próxima postagem nós vamos enviar dados simples para testarmos as funções.
Parabéns pela iniciativa. Faz um bom tempo que estou garimpando a net atrás de informações mais precisas sobre o uso do ENC, e este, certamente, está sendo o melhor artigo que já encontrei.
ResponderExcluirAbraço.
Paulo
Olá novamente.
ResponderExcluirSurgiu uma dúvida.
Ao final da função enc28j60PhyEscreve é chamada uma função tempo(), que função é esta?
Grato pela atenção
Galera, obrigado pelo apoio, este blog estava abandonado pela falta de tempo, mas vou reconstrui-lo novamente para nós entedermos este ENC, Eu vou trabalhar com uma prataforma real que eu contrui. Eu usei o AT89S8252o que é de costume dos amantes 8051. Ela é simples e facil de fazer, procurei colocar recursos minimos para entendemos melhor. ate+.
ResponderExcluirDeniro, poderia me passar teu e-mail?
ResponderExcluirAbraço
valdemiro.jr@ig.com.br
ResponderExcluir