Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

e2net

ENC28J60 chip에서 TCP/IP 서버 및 클라이언트 구현
by

Lee Hee-Chang

on 30 January 2013

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of e2net

Finally Receiving Packet main.c #include "system.h"

#include "net.h"

#include "ip_arp_udp_tcp.h"

#include "enc28j60.h"

#include "serial.h"




//MAC Address 설정

unsigned char e2net_mac[6] = {0x55,0x33,0xff,0x00,0x00,0x01};

//IP 설정

unsigned char e2net_ip[4] = {192,168,0,100};



//listen port

#define HTTP_PORT 80



#define BUFFER_SIZE 450

unsigned char buf[BUFFER_SIZE+1]; #if 1
void enc28j60RegDump(void)
{
printf("RevID: 0x%x\r\n", enc28j60Read(EREVID));

printf("Cntrl: ECON1 ECON2 ESTAT EIR EIE\r\n");
printf("%02x ",enc28j60Read(ECON1));
printf("%02x ",enc28j60Read(ECON2));
printf("%02x ",enc28j60Read(ESTAT));
printf("%02x ",enc28j60Read(EIR));
printf("%02x ",enc28j60Read(EIE));

printf("\r\nMAC : MACON1 MACON2 MACON3 MACON4 MAC-Address\r\n");
printf("%02x ",enc28j60Read(MACON1));
printf("%02x ",enc28j60Read(MACON2));
printf("%02x ",enc28j60Read(MACON3));
printf("%02x ",enc28j60Read(MACON4));
printf("%02x:",enc28j60Read(MAADR5));
printf("%02x:",enc28j60Read(MAADR4));
printf("%02x:",enc28j60Read(MAADR3));
printf("%02x:",enc28j60Read(MAADR2));
printf("%02x:",enc28j60Read(MAADR1));
printf("%02x",enc28j60Read(MAADR0));
printf("\r\n");
}
#endif int main(void)
{
unsigned int plen;
unsigned int dat_p;

//UART0 초기화
U0_Init(BAUD_115200);
printf("e2Net(ENC28_J60 Module) Test Program.\r\n");

//enc28j60 초기화
enc28j60Init(e2net_mac);
enc28j60clkout(2); //clkout 6.25MHz ~ 12.5MHz
Delay(10); // LED 초기화
Led1Init();
Led1Off();

enc28j60PhyWrite(PHLCON,0x476);
Delay(20);

Led1On();

enc28j60RegDump();

//MAC, IP 설정
init_ip_arp_udp_tcp(e2net_mac, e2net_ip, HTTP_PORT); while(1)
{
//이더넷으로 수신되된 데이터를 읽음
plen = enc28j60PacketReceive(BUFFER_SIZE, buf);

//수신된 데이터가 있으면
if(plen==0){
continue;
}
else
{
//printf("read=%d\r\n", plen);
}
uint16_t enc28j60PacketReceive(uint16_t maxlen, uint8_t* packet)
{
uint16_t rxstat;
uint16_t len;
// check if a packet has been received and buffered
if( enc28j60Read(EPKTCNT) ==0 ){
return(0);
} EPKTCNT (Ethernet Packet Count) Packet Filtering에 의해 필터링 되지 않은 패킷은 Circular receive buffer에 쓰여지게 되고, 이 때, EPKTCNT register는 1만큼 증가하게 됩니다. 따라서 0이 되면 Packet이 필터링 되었 거나, Packet이 수신 되지 않은 것을 의미합니다.
// Set the read pointer to the start of the received packet
enc28j60Write(ERDPTL, (NextPacketPtr));
enc28j60Write(ERDPTH, (NextPacketPtr)>>8);

// read the next packet pointer
NextPacketPtr = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8; Buffer Read Pointer (ERDPTH:ERDPTL) Host controller는 Read Buffer Memory(RBM) command를 이용하여 buffer memory 안의 데이터를 읽을 수 있습니다. ECON2 register 안의 AUTOINC bit를 1로 만들면, ERDPT pointer 자동으로 1씩 증가하며 연속된 데이타를 읽어오게 됩니다. 만약 Receive Buffer End pointer를 만나게 되면, 자동으로 Receive Buffer Start로 돌아 가게 됩니다. 이건 한정된 메모리로 연속적인 데이터 수신을 위한 것입니다. RBM(Read Buffer Memory) #define ENC28J60_READ_BUF_MEM 0x3A Read Buffer Memory (RBM) command는 host controller가 buffer memory 안에 데이터를 한 바이트 읽을 수 있기때문에, 소스에서처럼 2번 읽어서 처리해주어야합니다. // read the packet length (see datasheet page 43)
len = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
len-=4; //remove the CRC count


// read the receive status (see datasheet page 43)
rxstat = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8; // limit retrieve length
if (len>maxlen-1){
len=maxlen-1;
}

// check CRC and symbol errors (see datasheet page 44, table 7-3):
// The ERXFCON.CRCEN is set by default. Normally we should not
// need to check this.
if ((rxstat & 0x80)==0){
// invalid
len=0;
}else{
// copy the packet from the receive buffer
enc28j60ReadBuffer(len, packet);
}
Receive buffer 안의 Packet을 배열로 저장하고 마지막에 0을 붙여서 구분함. void enc28j60ReadBuffer(uint16_t len, uint8_t* data)
{
CSACTIVE;

SPI_WRITE(ENC28J60_READ_BUF_MEM);

while(len)
{
len--;
SPI_WRITE(0);

*data = SPI_READ();
data++;
}
*data='\0';
CSPASSIVE;
}
// Move the RX read pointer to the start of the next received packet
// This frees the memory we just read out
enc28j60Write(ERXRDPTL, (NextPacketPtr));
enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);

// decrement the packet counter indicate we are done with this packet
enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
return(len);
} Receive Buffer Read Pointer (ERXRDPT) ERXRDPT register 사용하여 FIFO 안에 하드웨어가 사용하면 안되는 위치를 정의해줍니다. 따라서 정상동작하고 있는 수신 하드웨어는 ERXRDPT가 가르키는 메모리까지 데이터를 저장 합니다. 연속적으로 데이터를 수신하기 위해서는 수신한 데이터를 처리할 때마다 이 포인터를 바꾸어 주어야 합니다. PKTDEC : Packet Decrement bit Packet 하나에 대해서 처리가 완료되었기 때문에 EPKTCNT register에서 하나 빼기 위해서 Ethernet control register 2에 있는 PKETDEC를 1로 만들어주는 것입니다. //ARP 처리
if(eth_type_is_arp_and_my_ip(buf,plen)){
make_arp_answer_from_request(buf);
continue;
}


//IP처리
if(eth_type_is_ip_and_my_ip(buf,plen)==0){
continue;
} uint8_t eth_type_is_arp_and_my_ip(uint8_t *buf,uint16_t len)
{
uint8_t i=0;

// eth + arp header is 42
if (len<41)
{
return(0);
}


if(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V ||
buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V)
{
return(0);
} Ehernet Packet Format #define ETH_TYPE_H_P 12
#define ETH_TYPE_L_P 13
// notation : _P = Position of a field

#define ETHTYPE_ARP_H_V 0x08
#define ETHTYPE_ARP_L_V 0x06
// notation : _V = Value of a field 0x0806 : ARP Protocol while(i<4){
if(buf[ETH_ARP_DST_IP_P+i] != ipaddr[i]){
return(0);
}
i++;
}
return(1);
} ARP Packet header #define ETH_ARP_DST_IP_P 0x26
// 0x26 = 38
// Ethenet header(14byte) + 26byte = 38byte ARP Protocol 주소 결정 프로토콜(Address Resolution Protocol, ARP)은 네트워크 상에서 IP 주소를 물리적 네트워크 주소로 대응시키기 위해 사용되는 프로토콜입니다. ARP는 크게 ARP Request와 ARP Reply로 나누어집니다. 그림에서 보듯이 지금은 ARP Request를 받았을 때, 이 메시지에서 물어 보는 곳의 IP주소와 자신의 IP가 같은 지 확인 하는 부분입니다. void make_arp_answer_from_request(uint8_t *buf)
{
uint8_t i=0;
make_eth(buf);
buf[ETH_ARP_OPCODE_H_P]=ETH_ARP_OPCODE_REPLY_H_V;
buf[ETH_ARP_OPCODE_L_P]=ETH_ARP_OPCODE_REPLY_L_V;
// fill the mac addresses:
while(i<6){
buf[ETH_ARP_DST_MAC_P+i]=buf[ETH_ARP_SRC_MAC_P+i];
buf[ETH_ARP_SRC_MAC_P+i]=macaddr[i];
i++;
} void make_eth(uint8_t *buf)
{

uint8_t i=0;

//copy the destination mac from the source and fill my mac into src
while(i<6)
{
buf[ETH_DST_MAC +i]=buf[ETH_SRC_MAC +i];
buf[ETH_SRC_MAC +i]=macaddr[i];
i++;
}
} ARP Reply 만들기 위해서 받은 Packet 내용을 이용하여 우선 Ethernet Header를 만드는 과정입니다. ARP_ OPCODE 0x0001 = ARP Request
0x0002 = ARP Reply
0x0003 = RARP Request
0x0004 = RARP Reply
uint8_t eth_type_is_ip_and_my_ip(uint8_t *buf,uint16_t len){
uint8_t i=0;
//eth+ip+udp header is 42
if (len<42){
return(0);
}
if(buf[ETH_TYPE_H_P]!=ETHTYPE_IP_H_V ||
buf[ETH_TYPE_L_P]!=ETHTYPE_IP_L_V){
return(0);
}
if (buf[IP_HEADER_LEN_VER_P]!=0x45){
// must be IP V4 and 20 byte header
return(0);
}
while(i<4){
if(buf[IP_DST_P+i]!=ipaddr[i]){
return(0);
}
i++;
}
return(1);
} #define ETH_TYPE_H_P 12
#define ETH_TYPE_L_P 13
// notation : _P = Position of a field #define ETHTYPE_IP_H_V 0x08
#define ETHTYPE_IP_L_V 0x00
// notation : _V = Value of a field 0x0800 : IP Protocol Ehernet Packet Format TCP Connection - 연결 설정 1단계 : 호스트 1이 호스트 2에게 연결요청정보가 담긴 세그먼트 전송 2단계 : 호스트 2는 호스트 1에게 확인응답 세그먼트 전송 3단계 : 호스트 2는 호스트 1에게 연결요청정보가 담긴 세그먼트 전송 4단계 : 호스트 1은 호스트 2에게 확인응답 세그먼트 전송 위의 2단계와 3단계는 한번에 수행가능하므로, 3단계로 줄일 수 있습니다. void make_tcp_synack_from_syn(uint8_t *buf)
{
uint16_t ck;
make_eth(buf);
// total length field in the IP header must be set:
// 20 bytes IP + 24 bytes (20tcp+4tcp options)
buf[IP_TOTLEN_H_P]=0;
buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4;
make_ip(buf);
buf[TCP_FLAGS_P]=TCP_FLAGS_SYNACK_V;
make_tcphead(buf,1,1,0);
// calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + 4 (one option: mss)
ck=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN+4,2);
buf[TCP_CHECKSUM_H_P]=ck>>8;
buf[TCP_CHECKSUM_L_P]=ck& 0xff;
// add 4 for option mss:
enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4+ETH_HEADER_LEN,buf);
} // tcp port www start, compare only the lower byte
if (buf[IP_PROTO_P]==IP_PROTO_TCP_V&&buf[TCP_DST_PORT_H_P]
==0&&buf[TCP_DST_PORT_L_P]==HTTP_PORT)
{
//SYN 응답
if(buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V)
{
make_tcp_synack_from_syn(buf);
continue;
} if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V)
{
init_len_info(buf); // init some data structures
// we can possibly have no data, just ack:
dat_p=get_tcp_data_pointer();

if(dat_p==0)
{
//FIN 응답
if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V)
{
make_tcp_ack_from_any(buf);
}
continue;
} TCP Connection - 연결종료 1단계 : 호스트 1은 연결종료 세그먼트 전송 2단계 : 호스트 2는 확인응답 세그먼트 전송(여전히 호스트2는 1로 전송가능) 3단계 : 호스트 2가 데이터 전송이 끝나면, 호스트 2는 연결종료 세그먼트 전송 4단계 : 호스트 1은 호스트 2에게 확인응답 세그먼트 전송 2단계와 3단계는 동시에 일어날 수도 있고, 개별적으로 일어날 수도 있음 // do some basic length calculations and store the result in static varibales

void init_len_info(uint8_t *buf)
{

info_data_len=(((int16_t)buf[IP_TOTLEN_H_P])<<8)|(buf[IP_TOTLEN_L_P]&0xff);
info_data_len-=IP_HEADER_LEN;
info_hdr_len=(buf[TCP_HEADER_LEN_P]>>4)*4;

// generate len in bytes;
info_data_len-=info_hdr_len;

if (info_data_len<=0)
{
info_data_len=0;
}

} #define IP_TOTLEN_H_P 0x10 #define IP_TOTLEN_L_P 0x11 IP 헤더의 바이트 숫자만큼 빼서 IP 데이터의 Payload를 구함 info_data_len -= IP_HEADER_LEN info_hdr_len=(buf[TCP_HEADER_LEN_P]>>4)*4; // get a pointer to the start of tcp data in buf
// Returns 0 if there is no data
// You must call init_len_info once before calling this function
uint16_t get_tcp_data_pointer(void)
{
if (info_data_len){
return((uint16_t)TCP_SRC_PORT_H_P+info_hdr_len);
}else{
return(0);
}
}
TCP header length는 IP header length와 달리 고정된 길이가 아니기 때문에 변하는 길이를 고려하여 포인터를 지정해 줌 // Make just an ack packet with no tcp data inside
// This will modify the eth/ip/tcp header
void make_tcp_ack_from_any(uint8_t *buf)
{
uint16_t j;
make_eth(buf);
// fill the header:
buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V;
if (info_data_len==0){
// if there is no data then we must still acknoledge one packet
make_tcphead(buf,1,0,1); // no options
}else{
make_tcphead(buf,info_data_len,0,1); // no options
}
Full transcript