Tải bản đầy đủ (.docx) (150 trang)

ATMEGA32 GIAO TIẾP VỚI ENC28J60 QUA SPI – AVR WEBSERVER

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.8 MB, 150 trang )

ATMEGA32 GIAO TIẾP VỚI ENC28J60 QUA SPI – AVR WEBSERVER
1. Hardware
Mô tả:
- Atmega32 giao tiếp với ENC28J60 qua SPI (MOSI/MISO/SCK) ngoài ra còn có chân chọn chip CS (nối với
bất cứ IO nào của Atmega) và ngắt INT (nối vào ngắt ngoài VĐK).
- ENC28J60 dùng nguồn 3V3, do đó cần 1 IC ổn áp 3V3. ENC28J60 cần 1 port RJ45 có tích hợp sẵn
Transformer và LED.
- Thêm MAX232 để dùng vào mục đích debug.
- Thêm LCD và keypad (dùng để config hay hiển thị gì đó sau này). Nếu không cần có thể bỏ ra.

2. Cơ sở giao thức và thiết kế lưu đồ dữ liệu
Cụ thể hóa và lưu đồ dữ liệu vào ra của giao thức (áp dụng cho phần lập trình)

Như vậy phần lập trình sẽ chia ra các module sau:
- Module điều khiển ENC28J60: nằm trong file “enc28j60.c” và file header “enc28j60.h”, thêm file
“enc28j60conf.h” để lưu các config.


- Module giao thức Ethernet: gồm các file: “ethernet.c” và “ethernet.h”, thêm file "packet.h" khai báo
các cấu trúc gói tin sử dụng trong bộ giao thức TCP/IP.
- Module giao thức phân giải địa chỉ Address Resolution Protocol, gồm file “arp.c” và “arp.h”
- Module giao thức IP gồm “ip.c” và “ip.h”
- Module giao thức cấp phát địa chỉ IP động DHCP (Dynamic Host Configuration Protocol) gồm các file
“dhcp.c” và “dhcp.h”
- Module giao thức UDP gồm các file “udp.c” và “udp.h”
- Module giao thức TCP gồm các file “tcp.c” và “tcp.h”
- Module giao thức HTTP gồm các file “http.c” và “http.h”
- Và một số các hàm hỗ trợ khác (uart, timer,…)

Sau khi tạo project, ta sẽ có source file đầu tiên là “ntAVRnet.c”.
Mở file này, thêm vào hàm main, chương trình chính, nội dung hàm này sẽ được viết cuối cùng


Code:
//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//---------------------------------------------------------------------------#include <avr/io.h>
//---------------------------------------------------------------------------int
{

main()
return(0);

}

Tạo thêm file header cho nó: “ntAVRnet.h” có nội dung:
Code:
//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//---------------------------------------------------------------------------#ifndef NTAVRNET_H
#define NTAVRNET_H
#endif //NTAVRNET_H

File này sẽ dùng để chứa các define và thông tin config chung cho toàn project


Bài 3: Lập trình điều khiển ENC28J60:
Phần này có tham khảo các project open source của nước ngoài, thông tin về tài liệu tham khảo
sẽ được nêu cụ thể ở cuối tut
Tạo các file “enc28j60.c”, “enc28j60.h” và “enc28j60conf.h”. Add vào project
ENC28J60 được điều khiển bởi một tập khá lớn các thanh ghi điều khiển, dữ liệu (frame ehternet
gửi/nhận) được lưu trữ trên 1 buffer. Việc đọc/ghi vào các thanh ghi điều khiển cũng như buffer
dữ liệu được thực hiện qua giao tiếp SPI tới 1 địa chỉ xác định.
Mở file enc28j60.h, khai báo địa chỉ các thanh ghi vào file
Code:

//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//----------------------------------------------------------------------------

#ifndef ENC28J60_H
#define ENC28J60_H
// 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
// All-bank registers
#define EIE
0x1B
#define EIR
0x1C
#define ESTAT
0x1D
#define ECON2
0x1E
#define ECON1
0x1F

// Bank 0 registers
#define ERDPTL
(0x00|0x00)
#define ERDPTH
(0x01|0x00)
#define EWRPTL
(0x02|0x00)
#define EWRPTH
(0x03|0x00)
#define ETXSTL
(0x04|0x00)
#define ETXSTH
(0x05|0x00)
#define ETXNDL
(0x06|0x00)
#define ETXNDH
(0x07|0x00)
#define ERXSTL
(0x08|0x00)
#define ERXSTH
(0x09|0x00)
#define ERXNDL
(0x0A|0x00)
#define ERXNDH
(0x0B|0x00)
#define ERXRDPTL
(0x0C|0x00)
#define ERXRDPTH
(0x0D|0x00)
#define ERXWRPTL

(0x0E|0x00)
#define ERXWRPTH
(0x0F|0x00)
#define EDMASTL
(0x10|0x00)
#define EDMASTH
(0x11|0x00)
#define EDMANDL
(0x12|0x00)
#define EDMANDH
(0x13|0x00)
#define EDMADSTL
(0x14|0x00)


#define
#define
#define
// Bank
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define

#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
// Bank
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define

#define
#define
#define
// Bank
#define
#define
#define
#define
#define
#define
#define
#define

EDMADSTH
EDMACSL
EDMACSH
1 registers
EHT0
EHT1
EHT2
EHT3
EHT4
EHT5
EHT6
EHT7
EPMM0
EPMM1
EPMM2
EPMM3
EPMM4

EPMM5
EPMM6
EPMM7
EPMCSL
EPMCSH
EPMOL
EPMOH
EWOLIE
EWOLIR
ERXFCON
EPKTCNT
2 registers
MACON1
MACON2
MACON3
MACON4
MABBIPG
MAIPGL
MAIPGH
MACLCON1
MACLCON2
MAMXFLL
MAMXFLH
MAPHSUP
MICON
MICMD
MIREGADR
MIWRL
MIWRH
MIRDL

MIRDH
3 registers
MAADR1
MAADR0
MAADR3
MAADR2
MAADR5
MAADR4
EBSTSD
EBSTCON

(0x15|0x00)
(0x16|0x00)
(0x17|0x00)
(0x00|0x20)
(0x01|0x20)
(0x02|0x20)
(0x03|0x20)
(0x04|0x20)
(0x05|0x20)
(0x06|0x20)
(0x07|0x20)
(0x08|0x20)
(0x09|0x20)
(0x0A|0x20)
(0x0B|0x20)
(0x0C|0x20)
(0x0D|0x20)
(0x0E|0x20)
(0x0F|0x20)

(0x10|0x20)
(0x11|0x20)
(0x14|0x20)
(0x15|0x20)
(0x16|0x20)
(0x17|0x20)
(0x18|0x20)
(0x19|0x20)
(0x00|0x40|0x80)
(0x01|0x40|0x80)
(0x02|0x40|0x80)
(0x03|0x40|0x80)
(0x04|0x40|0x80)
(0x06|0x40|0x80)
(0x07|0x40|0x80)
(0x08|0x40|0x80)
(0x09|0x40|0x80)
(0x0A|0x40|0x80)
(0x0B|0x40|0x80)
(0x0D|0x40|0x80)
(0x11|0x40|0x80)
(0x12|0x40|0x80)
(0x14|0x40|0x80)
(0x16|0x40|0x80)
(0x17|0x40|0x80)
(0x18|0x40|0x80)
(0x19|0x40|0x80)
(0x00|0x60|0x80)
(0x01|0x60|0x80)
(0x02|0x60|0x80)

(0x03|0x60|0x80)
(0x04|0x60|0x80)
(0x05|0x60|0x80)
(0x06|0x60)
(0x07|0x60)


#define EBSTCSL
#define EBSTCSH
#define MISTAT
#define EREVID
#define ECOCON
#define EFLOCON
#define EPAUSL
#define EPAUSH
// PHY registers
#define PHCON1
#define PHSTAT1
#define PHHID1
#define PHHID2
#define PHCON2
#define PHSTAT2
#define PHIE
#define PHIR
#define PHLCON

(0x08|0x60)
(0x09|0x60)
(0x0A|0x60|0x80)
(0x12|0x60)

(0x15|0x60)
(0x17|0x60)
(0x18|0x60)
(0x19|0x60)
0x00
0x01
0x02
0x03
0x10
0x11
0x12
0x13
0x14

// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE
0x80
#define EIE_PKTIE
0x40
#define EIE_DMAIE
0x20
#define EIE_LINKIE
0x10
#define EIE_TXIE
0x08
#define EIE_WOLIE
0x04
#define EIE_TXERIE
0x02
#define EIE_RXERIE

0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF
0x40
#define EIR_DMAIF
0x20
#define EIR_LINKIF
0x10
#define EIR_TXIF
0x08
#define EIR_WOLIF
0x04
#define EIR_TXERIF
0x02
#define EIR_RXERIF
0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT
0x80
#define ESTAT_LATECOL 0x10
#define ESTAT_RXBUSY
0x04
#define ESTAT_TXABRT
0x02
#define ESTAT_CLKRDY
0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC 0x80
#define ECON2_PKTDEC
0x40

#define ECON2_PWRSV
0x20
#define ECON2_VRPS
0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST
0x80
#define ECON1_RXRST
0x40
#define ECON1_DMAST
0x20
#define ECON1_CSUMEN
0x10
#define ECON1_TXRTS
0x08
#define ECON1_RXEN
0x04
#define ECON1_BSEL1
0x02
#define ECON1_BSEL0
0x01
// ENC28J60 MACON1 Register Bit Definitions


#define MACON1_LOOPBK 0x10
#define MACON1_TXPAUS 0x08
#define MACON1_RXPAUS 0x04
#define MACON1_PASSALL 0x02
#define MACON1_MARXEN 0x01
// ENC28J60 MACON2 Register Bit Definitions

#define MACON2_MARST
0x80
#define MACON2_RNDRST 0x40
#define MACON2_MARXRST 0x08
#define MACON2_RFUNRST 0x04
#define MACON2_MATXRST 0x02
#define MACON2_TFUNRST 0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2 0x80
#define MACON3_PADCFG1 0x40
#define MACON3_PADCFG0 0x20
#define MACON3_TXCRCEN 0x10
#define MACON3_PHDRLEN 0x08
#define MACON3_HFRMLEN 0x04
#define MACON3_FRMLNEN 0x02
#define MACON3_FULDPX 0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN 0x02
#define MICMD_MIIRD
0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID 0x04
#define MISTAT_SCAN
0x02
#define MISTAT_BUSY
0x01
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST
0x8000
#define PHCON1_PLOOPBK 0x4000

#define PHCON1_PPWRSV 0x0800
#define PHCON1_PDPXMD 0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX 0x1000
#define PHSTAT1_PHDPX 0x0800
#define PHSTAT1_LLSTAT 0x0004
#define PHSTAT1_JBSTAT 0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK 0x4000
#define PHCON2_TXDIS
0x2000
#define PHCON2_JABBER 0x0400
#define PHCON2_HDLDIS 0x0100
// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN
0x08
#define PKTCTRL_PPADEN 0x04
#define PKTCTRL_PCRCEN 0x02
#define PKTCTRL_POVERRIDE
0x01
#endif //ENC28J60_H
//----------------------------------------------------------------------------

Khi giao tiếp với ENC28J60 qua SPI, ngoài địa chỉ thì còn có Operating code điều khiển thao tác
đọc/ghi/…
Thêm định nghĩa các code này vào file trên (trên dòng #endif //ENC28J60_H nhé)


Code:
// SPI operation codes

#define ENC28J60_READ_CTRL_REG 0x00
#define ENC28J60_READ_BUF_MEM 0x3A
#define ENC28J60_WRITE_CTRL_REG
#define ENC28J60_WRITE_BUF_MEM 0x7A
#define ENC28J60_BIT_FIELD_SET 0x80
#define ENC28J60_BIT_FIELD_CLR 0xA0
#define ENC28J60_SOFT_RESET

0x40

0xFF

Khai báo địa chỉ bắt đầu và kết thúc buffer dữ liệu gửi và nhận trên ENC28J60:
Code:
#define
#define
#define
#define

TXSTART_INIT
TXSTOP_INIT
RXSTART_INIT
RXSTOP_INIT

0x0000 //Dia
0x05FF //Dia chi ket
0x0600 //Dia
0x1FFF //Dia

chi bat dau buffer gui

thuc buffer gui
chi bat dau buffer nhan
chi ket thuc buffer nhan

//Khai bao kich thuoc frame ethernet max va min
#define MAX_FRAMELEN
1518
#define ETHERNET_MIN_PACKET_LENGTH
0x3C

Tiếp theo, mở file enc28j60conf.h, thêm vào đó các khai báo về IO port sử dụng điều khiển
ENC28J60 và một số thông tin cấu hình khác (địa chỉ MAC)
Code:
//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//----------------------------------------------------------------------------

#ifndef ENC28J60CONF_H
#define ENC28J60CONF_H
//
//Khai bao cac chan IO cho ENC28J60
#define ENC28J60_CONTROL_DDR
DDRB
#define ENC28J60_CONTROL_PORT PORTB
#define ENC28J60_SPI_DDR
DDRB
#define ENC28J60_SPI_PORT
PORTB
//
#define ENC28J60_CONTROL_CS
3

#define ENC28J60_CONTROL_RESET 4
#define ENC28J60_SPI_SCK
7
#define ENC28J60_SPI_MISO
6
#define ENC28J60_SPI_MOSI
5
#define ENC28J60_SPI_SS
4
#define ENC28J60_SPI_CS
3
//
//Dinh nghia macro chon chip ENC28J60
#define ENC28J60_CS_LO()
ENC28J60_CONTROL_PORT &=
~(1<#define ENC28J60_CS_HI()
ENC28J60_CONTROL_PORT |=
(1<//
#define ETH_INTERRUPT
INT2_vect


//
#if defined (__AVR_ATmega32__)
#define ETH_INT_ENABLE
GICR |= (1<#define ETH_INT_DISABLE GICR &= ~(1<#endif

#if defined (__AVR_ATmega644__) || defined (__AVR_ATmega644P__)
#define ETH_INT_ENABLE
EIMSK |= (1<#define ETH_INT_DISABLE EIMSK &= ~(1<#endif
// MAC address for this interface
#ifdef ETHADDR0
#define ENC28J60_MAC0 ETHADDR0
#define ENC28J60_MAC1 ETHADDR1
#define ENC28J60_MAC2 ETHADDR2
#define ENC28J60_MAC3 ETHADDR3
#define ENC28J60_MAC4 ETHADDR4
#define ENC28J60_MAC5 ETHADDR5
#else
#define ENC28J60_MAC0 '0'
#define ENC28J60_MAC1 'F'
#define ENC28J60_MAC2 'F'
#define ENC28J60_MAC3 'I'
#define ENC28J60_MAC4 'C'
#define ENC28J60_MAC5 'E'
#endif
#endif // ENC28J60CONF_H
//----------------------------------------------------------------------------

Trước hết ta cần 1 hàm đọc và 1 hàm ghi dữ liệu vào 1 địa chỉ xác định trên ENC28J60 qua SPI:
Mở file enc28j60.c, thêm vào các hàm (sau đó nhớ thêm khai báo hàm vào file header
enc28j60.h nữa nhé)
Code:

//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM

//---------------------------------------------------------------------------#include <avr/io.h>
#include "ntAVRnet.h"
#include "enc28j60.h"
#include "enc28j60conf.h"
#include <avr/pgmspace.h>
//---------------------------------------------------------------------------unsigned char enc28j60SPIRead(unsigned char op, unsigned char address)
{
unsigned char res;
ENC28J60_CS_LO();
SPDR = op | (address & ADDR_MASK);
while(!(SPSR & (1<SPDR = 0x00;
while(!(SPSR & (1<if(address & 0x80){
SPDR = 0x00;
while(!((SPSR) & (1<

}
res = SPDR;
ENC28J60_CS_HI();
return res;
}
void enc28j60SPIWrite(unsigned char op, unsigned char address, unsigned char
data)
{
ENC28J60_CS_LO();
SPDR = op | (address & ADDR_MASK);
while(!(SPSR & (1<SPDR = data;

while(!(SPSR & (1<ENC28J60_CS_HI();
}

ENC28J60 chia tập thanh ghi thành các bank, ta viết 1 hàm để set bank thanh ghi:
Trước hết ta khai báo 1 biến kiểu char để lưu bank hiện tại (thêm vào đầu file):
Code:
unsigned char Enc28j60Bank;

Và viết hàm:
Code:

void enc28j60SetBank(unsigned char address)
{
if((address & BANK_MASK) != Enc28j60Bank)
{
enc28j60SPIWrite(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|
ECON1_BSEL0));
enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, ECON1, (address &
BANK_MASK)>>5);
Enc28j60Bank = (address & BANK_MASK);
}
}

Để phục vụ cho việc đọc và ghi buffer dữ liệu, ta viết tiếp 2 hàm đọc và ghi buffer:
Code:
void enc28j60ReadBuffer(unsigned int len, unsigned char* data)
{
ENC28J60_CS_LO();
SPDR = ENC28J60_READ_BUF_MEM;

while(!(SPSR & (1<while(len--)
{
SPDR = 0x00;
while(!(SPSR & (1<*data++ = SPDR;
}
ENC28J60_CS_HI();
}
void enc28j60WriteBuffer(unsigned int len, unsigned char* data)


{

ENC28J60_CS_LO();
SPDR = ENC28J60_WRITE_BUF_MEM;
while(!(SPSR & (1<while(len--)
{
SPDR = *data++;
while(!(SPSR & (1<}
ENC28J60_CS_HI();

}

Dựa vào cơ sở các hàm này, ta xây dựng các hàm đọc ghi thanh ghi điều khiển, thanh ghi PHY
của ENC28J60:
Code:


unsigned char enc28j60Read(unsigned char address)
{
enc28j60SetBank(address);
return enc28j60SPIRead(ENC28J60_READ_CTRL_REG, address);
}
void enc28j60Write(unsigned char address, unsigned char data)
{
enc28j60SetBank(address);
enc28j60SPIWrite(ENC28J60_WRITE_CTRL_REG, address, data);
}
unsigned int enc28j60PhyRead(unsigned char address)
{
unsigned int data;
enc28j60Write(MIREGADR, address);
enc28j60Write(MICMD, MICMD_MIIRD);
while(enc28j60Read(MISTAT) & MISTAT_BUSY);
enc28j60Write(MICMD, 0x00);
data = enc28j60Read(MIRDL);
data |= enc28j60Read(MIRDH);
return data;
}
void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
enc28j60Write(MIREGADR, address);
enc28j60Write(MIWRL, data);
enc28j60Write(MIWRH, data>>8);
while(enc28j60Read(MISTAT) & MISTAT_BUSY);
}

Các hàm trên cho phép truy xuất đầy đủ vào tập thanh ghi của ENC28J60

Tiếp theo ta viết tiếp 2 hàm gửi và nhận 1 gói tin:
Code:
void enc28j60PacketSend(unsigned int len, unsigned char* packet)
{
enc28j60Write(EWRPTL, TXSTART_INIT);
enc28j60Write(EWRPTH, TXSTART_INIT>>8);


enc28j60Write(ETXNDL, (TXSTART_INIT+len));
enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);
enc28j60SPIWrite(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
enc28j60WriteBuffer(len, packet);
enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
}
unsigned int NextPacketPtr;
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
{
unsigned int rxstat;
unsigned int len;
if( !enc28j60Read(EPKTCNT) )
return 0;
enc28j60Write(ERDPTL, (NextPacketPtr));
enc28j60Write(ERDPTH, (NextPacketPtr)>>8);
NextPacketPtr = enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0);
NextPacketPtr |= ((unsigned int)enc28j60SPIRead(ENC28J60_READ_BUF_MEM,
0))<<8;
len = enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0);
len |= ((unsigned int)enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0))<<8;
rxstat = enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0);
rxstat |= ((unsigned int)enc28j60SPIRead(ENC28J60_READ_BUF_MEM,

0))<<8;
len = ((lenenc28j60ReadBuffer(len, packet);
enc28j60Write(ERXRDPTL, (NextPacketPtr));
enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);
enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
}

return len;

Và hàm khởi động IC ENC28J60:
Để có thể nhanh chóng truy xuất thông tin cấu hình khởi động cho ENC28J60, ta lưu toàn bộ
thông tin này vào một array trong bộ nhớ flash chứa địa chỉ các thanh ghi và giá trị khởi tạo
tương ứng, khai báo array này trong file enc28j60.c:
Code:
prog_char enc28j60_config[44] PROGMEM = {
ETXSTL, LO8(TXSTART_INIT), //start lo
ETXSTH, HI8(TXSTART_INIT), //start hi
ETXNDL, LO8(TXSTOP_INIT ), //end lo
ETXNDH, HI8(TXSTOP_INIT ), //end hi
ERXSTL,
ERXSTH,
ERXNDL,
ERXNDH,
MACON2,
MACON1,
MACON3,

LO8(RXSTART_INIT), //start lo
HI8(RXSTART_INIT), //start hi

LO8(RXSTOP_INIT ), //end lo
HI8(RXSTOP_INIT ), //end hi
0x00,
(MACON1_MARXEN | MACON1_RXPAUS | MACON1_TXPAUS),
( MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN),


MAMXFLL,
MAMXFLH,
MABBIPG,
MAIPGL,
MAIPGH,
MAADR5,
MAADR4,
MAADR3,
MAADR2,
MAADR1,
MAADR0,
};

LO8(1518),
HI8(1518),
0x12, //half duplex
0x12,
0x0C, //half duplex
ENC28J60_MAC0,
ENC28J60_MAC1,
ENC28J60_MAC2,
ENC28J60_MAC3,
ENC28J60_MAC4,

ENC28J60_MAC5

Trong hàm này, ta sẽ cần hàm delay_us để delay
Code:

void delay_us(unsigned short time_us)
{
unsigned short delay_loops;
register unsigned short i;
(dirty)

}

delay_loops = (time_us+3)/5*CYCLES_PER_US; // +3 for rounding up
// one loop takes 5 cpu cycles
for (i=0; i < delay_loops; i++) {};

Trong hàm delay trên ta cần 1 thông tin là số chu kỳ máy của CPU/micro giây, do đó ta cần thêm
các thông tin này vào file cấu hình chung:
Mở file ntAVRnet.h, thêm vào các define sau:
Code:

//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//---------------------------------------------------------------------------#ifndef NTAVRNET_H
#define NTAVRNET_H
#ifndef F_CPU
#define F_CPU
12000000 // Cho toc do la 12MHz
#endif //F_CPU
#define CYCLES_PER_US ((F_CPU+500000)/1000000)

//So chu ky lenh trong 1
micro giay
#define LO8(x) ((x)&0xFF)
#define HI8(x) (((x)>>8)&0xFF)
#endif //NTAVRNET_H

Và đây là hàm khởi động ENC28J60:


Code:
void enc28j60Init(void)
{
unsigned char i;
unsigned int timeout=0;
Enc28j60Bank = 0xFF;
ENC28J60_CONTROL_DDR |= (1<ENC28J60_CS_HI();
ENC28J60_SPI_PORT |= (1<ENC28J60_SPI_DDR |= (1<(1<ENC28J60_SPI_DDR &= ~(1<SPCR = (0<(0<SPSR = (1<delay_us(65000);delay_us(65000);delay_us(65000);
enc28j60SPIWrite(ENC28J60_SOFT_RESET,0, ENC28J60_SOFT_RESET);
delay_us(65000);delay_us(65000);delay_us(65000);
while((!(enc28j60Read(ESTAT) & 0x01)) && (timeout<65000)){timeout++;};
if(timeout>=65000){timeout=0;}
NextPacketPtr = RXSTART_INIT;

for(i=0; i<2*22; i+=2){
enc28j60Write(pgm_read_byte(&enc28j60_config[i+0]),pgm_read_byte(&enc28j60_con
fig[i+1]));
}
enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS); //=no loopback of transmitted
frames
enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
enc28j60PhyWrite(PHLCON, 0x347A);
}

Cuối cùng, nhớ thêm phần khai báo tất cả các hàm đã viết trên vào file header (enc28j60.h) điều
này sẽ giúp truy xuất dễ dàng các hàm này từ các module khác.
Bài tiếp theo sẽ là lập trình giao thức ethernet.
Thành viên chính thức

Tham gia
Jan 2010
Bài viết
60
HI Anh nttam79, Anh học ở PTITHCM à? Em là SV khoa Điện tử ở PTITHCM. May ghê, em
đang làm đề tài AVR-Webserver. Cũng cậm cụi đang vẽ mạch in, đọc code làm anh ạ? Hi vọng
anh tiếp tục hướng dẫn làm về project này. Anh cho em xin mail hoặc SĐT nha. Tks Anh!
Anh mún cho code vào khung thì anh wrap[quote] html[quote] php[quote]=> ([QUOTE]copy


code here[QUOTE]) là okey anh.
Và kết quả là như thế này:
unsigned char enc28j60SPIRead(unsigned char op, unsigned char address)
{

unsigned char res;
ENC28J60_CS_LO();
SPDR = op | (address & ADDR_MASK);
while(!(SPSR & (1<SPDR = 0x00;
while(!(SPSR & (1<if(address & 0x80){
SPDR = 0x00;
while(!((SPSR) & (1<}
res = SPDR;
ENC28J60_CS_HI();
return res;
}


Bài 4: Lập trình giao thức ethernet
HOẠT ĐỘNG CỦA TCP/IP
- Dữ liệu truyền từ ứng dụng, đến một trong 2 giao thức vận chuyển (TCP hay UDP). Một gói tin
hay đơn vị dữ liệu (PDU) của TCP/UDP thường được gọi là segment (đoạn dữ liệu).
- Đoạn dữ liệu xuống lớp Internet, ở đó giao thức IP cung cấp thông tin đánh địa chỉ luận lý (địa
chỉ IP) và đóng gói dữ liệu vào 1 datagram, thường được gọi là gói tin IP (IP packet).
- Datagram IP đến lớp truy cập mạng (ở đây là giao thức ethernet), và được đóng gói vào 1 hay
nhiều khung dữ liệu (frame ethernet), sau đó đưa xuống tầng vật lý (ví dụ IC ENC28J60) để gửi
đi. Khung dữ liệu được chuyển thành một luồng các bit để truyền đi trên môi trường mạng.
Ở phía thu, quá trình xảy ra ngược lại, tầng vật lý sẽ nhận luồng bit, khôi phục lại frame dữ liệu,
giao thức ethernet phía nhận sẽ xử lý frame dữ liệu này, tách ra gói tin IP (IP packet) và đẩy lên
giao thức IP nếu đây là gói IP. Còn trong trường hợp bên trong frame ethernet không phải là 1
gói IP mà là 1 gói tin của giao thức ARP thì nó sẽ đẩy gói này sang cho giao thức ARP xử lý
(xem lại hình minh họa lưu đồ dữ liệu giữa các giao thức).

Tại tầng giao thức IP, gói IP sẽ được xử lý, xác định xem dữ liệu chứa bên trong là của giao thức
nào (TCP, UDP, hay ICPM) và chuyển đến giao thức tương ứng để xử lý tiếp theo.
Cuối cùng, giao thức kế (TCP, UDP, hay ICMP sẽ xử lý tiếp segment dữ liệu nhận được, xác
định xem dữ liệu này là của ứng dụng nào (ví dụ như HTTP hay DHCP,…) và chuyển dữ liệu
đến ứng dụng tương ứng
Trước hết, ta cần nắm rõ cấu trúc của tất cả các gói tin của mỗi giao thức để có thể xử lý được
thông tin chứa trong nó.
Ta tạo 1 file header mới là “packet.h”, file này sẽ dùng để chứa mô tả cấu trúc của tất cả các gói
tin của các giao thức sử dụng trong project này:
Code:

//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//---------------------------------------------------------------------------#ifndef PACKET_H
#define PACKET_H
#endif //PACKET_H

Trước hết ta tìm hiểu cấu trúc 1 frame ethernet:

Như vậy 1 frame ethernet bắt đầu bắng các byte Preamble để dồng bộ và 1 byte Start of Frame
để xác định đầu frame (phần này sẽ được ENC28J60 tự động lược bỏ, ta không cần quan tâm).
Tiếp theo là địa chỉ MAC của host nhận (destination address), địa chỉ MAC của host gửi (source
address), mỗi địa chỉ MAC này gồm 6 byte. Kế đến là 2 byte length (cho biết chiều dài) hoặc
type (cho biết dữ liệu chứa trong frame là loại dữ liệu của giao thức lớp trên nào). Kế đến là dữ
liệu. Cuối cùng là phần kiểm tra lỗi (FCS), phần này cũng được ENC28J60 xử lý, ta không quan
tâm.
Như vậy ta cần khai báo cấu trúc của header frame ethernet (từ phần địa chỉ đến 2 byte type)
trong file “packet.h”
Trước hết ta định nghĩa 1 struc cho địa chỉ MAC:



Code:
//------------------------------------------------------------------------------------//Dia chi vat ly hay dia chi MAC (lop Ethernet)
struct ntEthAddr
{
unsigned char addr[6];
};

Kế đến là header của frame ethernet:
Code:

//------------------------------------------------------------------------------------//Ethernet header
// Gom 14 byte:
// 06 byte dia chi dich
// 06 byte dia chi nguon
// 02 byte type (cho biet frame ethernet mang ben trong loai du lieu gi)
#define ETH_HEADER_LEN 14
struct ntEthHeader
{
struct ntEthAddr desAddr;
struct ntEthAddr srcAddr;
unsigned int type;
};

Ta cũng định nghĩa các giá trị hằng qui định cho trường Type (tham khảo tài liệu về ethernet để
biết thêm các giá trị của trường này):
Code:

//
#define ETH_TYPE_ARP 0x0806
#define ETH_TYPE_IP 0x0800

#define ETH_TYPE_IP6 0x86dd

Nhân tiện ở đây cũng định nghĩa luôn cấu trúc của các frame ethernet cho VLAN (802.1q) và
VLAN Q-inQ (802.1ad), cấu trúc của header MPLS. Đây là các giao thức sữ dụng phổ biến trên
mạng MEN (hay MAN-E) của VNPT. Bạn nào không quan tâm đến các giao thức này có thể bỏ
qua, ta chỉ tập trung vào giao thức ethernet thuần túy.
Code:

//------------------------------------------------------------------------------------//Ethernet header 802.1q VLAN Tagging
struct ntEth802_1qHeader
{
struct ntEthAddr desAddr;
struct ntEthAddr srcAddr;
unsigned int type;
unsigned int TPID;
unsigned int PCP_CFI_VID;


};
#define ETH_802_1Q_HEADER_LEN 18
//
#define ETH_802_1Q_TPID
0x8100
#define ETH_802_1Q_PCP_MASK
0xE000
#define ETH_802_1Q_CFI_MASK
0x1000
#define ETH_802_1Q_VID_MASK
0x0FFF
//------------------------------------------------------------------------------------//Ethernet header 802.1ad Q-in-Q VLAN Tagging

struct ntEth802_1adHeader
{
struct ntEthAddr desAddr;
struct ntEthAddr srcAddr;
unsigned int type;
unsigned int OuterTPID;
unsigned int OuterPCP_CFI_VID;
unsigned int InnerTPID;
unsigned int InnerPCP_CFI_VID;
};
#define ETH_802_1AD_HEADER_LEN
22
//
#define ETH_802_1AD_TPID
0x88a8
#define ETH_802_QINQ_TPID1
0x9100
#define ETH_802_QINQ_TPID2
0x9200
#define ETH_802_QINQ_TPID3
0x9300
//------------------------------------------------------------------------------------//Cau truc MPLS Header
struct ntMPLSHeader
{
unsigned int
HighLabelValue;
unsigned char TrafficClass_Stack;
unsigned char TTL;
};
#define MPLS_HEADER_LEN

4
//
#define MPLS_LOW_LABEL_MASK
0xF0
#define MPLS_TRF_CLS_MASK
0x0E
#define MPLS_STACK_MASK
0x01
//

Vậy là xong phần khai báo cấu trúc frame ethernet. Tiếp theo là viết các hàm xử lý giao thức
ethernet.
Trong chồng giao thức TCP/IP, giao thức ethernet đóng vai trò lớp truy nhập và truyền dẫn. Việc
gửi và nhận dữ liệu ở lớp ethernet được thực hiện dựa vào địa chỉ vật lý hay còn gọi là địa chỉ
MAC.
Trong mỗi frame ethernet đều chứa 2 địa chỉ MAC: một địa chỉ của host gửi và 1 địa chỉ của host
nhận
khi lớp ethernet nhận được 1 frame dữ liệu, trước hết nó sẽ kiểm tra địa chỉ host nhận xem có
phải là địa chỉ của nó không (tức là gửi cho nó), nếu đúng nó sẽ nhận frame này và chuyển đến
lớp IP. Ngoài ra còn có 1 trường hợp nữa lớp ehternet sẽ nhận frame: đó là nếu địa chỉ host nhận
là địa chỉ broadcast (tức là gửi cho tất cả mọi máy trong mạng LAN), trong trường hợp này
frame sẽ được nhận và xử lý.


Ngoài việc kiểm tra địa chỉ, trong frame ethernet còn có 1 trường chứa mã kiểm tra lỗi giúp phát
hiện những lỗi xảy ra trong quá trình truyền, các frame bị xác định là có lỗi sẽ bị bỏ qua.
Trong mạch của chúng ta, việc kiểm tra lỗi và kiểm tra địa được thực hiện tự động bởi IC
ENC28J60, do đó ta không cần lập trình cho các chức năng này. Mỗi khi nhận được 1 frame trên
đường truyền, ENC28J60 sẽ kiểm tra lỗi xem có sai sót không, tiếp đó nó sẽ đối chiếu địa chỉ
host nhận với địa chỉ đã được cấu hình cho nó (trong các thanh ghi địa chỉ MAC: MAADR0-5).

Nếu không có lỗi và địa chỉ là gửi cho nó, nó sẽ tạo 1 ngắt cứng (trên chân INT của ENC28J60)
để báo cho VĐK biết là nó vừa nhận được 1 frame hợp lệ và yêu cầu VĐK xử lý frame này.
Vậy công việc của chúng ta là viết hàm xử lý cho trường hợp này, cũng như cung cấp 1 hàm gửi
đi 1 frame dữ liệu (để sử dụng khi muốn gửi dữ liệu đi). Bên cạnh đó ta cũng cần một số hàm
cung cấp các chức năng bổ sung như set/get địa chỉ MAC,…
Tạo 1 file source "ethernet.c" để viết module ethernet và file header cho nó "ethernet.h"
File ethernet.c
Code:

//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//---------------------------------------------------------------------------#include <avr/io.h>
#include <avr/pgmspace.h>
#include "packet.h"
#include "enc28j60.h"
#include "enc28j60conf.h"
#include "ethernet.h"

File ethernet.h
Code:
//---------------------------------------------------------------------------// Writen by NTTam - PTITHCM
//---------------------------------------------------------------------------#ifndef ETHERNET_H
#define ETHERNET_H
#endif //ETHERNET_H

Lập trình cho giao thức ethernet (tiếp theo)
Trước hết, ta khai báo 1 buffer trên bộ nhớ RAM của VĐK để lưu trữ frame dữ liệu mà ta cần xử
lý.
Kích thước của buffer này sẽ bằng kích thước lớn nhất của 1 segment dữ liệu mà hệ thống của ta
có thể xử lý (MTU) + kích thước header TCP + kích thước header UDP + kích thước header của
frame.

Trong đó:
- Với giao thức TCP/IP thì MTU max là 1460
- Kích thước Header của TCP là 20 bytes
- Kích thước Header của IP là 20 bytes (ở đây xem như không có trường Option trong Header IP,


cấu trúc gói IP sẽ được giải thích chi tiết ở phần lập trình cho giao thức IP).
- Kích thước Header của frame Ethernet là 14 byte.
Vậy trước hết ta thêm các khai báo kích thước này vào file “ethernet.h”
Code:
#ifndef
#endif
#ifndef

#define

MTU_SIZE
MTU_SIZE

1460

IP_HEADER_LEN
#define IP_HEADER_LEN 20
//IP_HEADER_LEN
TCP_HEADER_LEN
TCP_HEADER_LEN 20
//TCP_HEADER_LEN

#endif
#ifndef

#define
#endif
//
#ifndef ETHERNET_BUFFER_SIZE
#define ETHERNET_BUFFER_SIZE
(700+ETH_HEADER_LEN+IP_HEADER_LEN+TCP_HEADER_LEN)
#endif

Sở dĩ ta thêm điều kiện (#ifndef … #endif) là vì thực ra kích thước Header TCP và IP sẽ được
định nghĩa trong file packet.h, MTU_SIZE sẽ được định nghĩa trong phần config thông tin chung
của chương trình, do hiện nay ta chưa viết các phần đó nên tạm thời định nghĩa trước.
Sau đó khai báo buffer dành cho frame ethernet trong file source (“ethernet.c”):
Code:
unsigned char ethBuffer[ETHERNET_BUFFER_SIZE];

Tiếp theo ta viết một số hàm cung cấp các chức năng cơ bản cho lớp ethernet:
Code:

//---------------------------------------------------------------------------//Ham khoi tao chip Ethernet
void ethInit(void)
{
enc28j60Init();
ETH_INT_ENABLE;
}
//------------------------------------------------------------------------------------//Ham goi 1 frame xuong chip ethernet
void ethSendFrame(unsigned int len, unsigned char* packet)
{
enc28j60PacketSend(len, packet);
}
//------------------------------------------------------------------------------------//Ham doc 1 frame ethernet tu chip ethernet ve buffer tren RAM cua CPU

unsigned int ethGetFrame(unsigned int maxlen, unsigned char* packet)
{
return enc28j60PacketReceive(maxlen, packet);
}
//------------------------------------------------------------------------------------//Ham doc dia chi MAC hien tai tu chip ethernet, luu vao buffer macaddr[6]
void ethGetMacAddress(unsigned char* macaddr)
{


*macaddr++
*macaddr++
*macaddr++
*macaddr++
*macaddr++
*macaddr++

=
=
=
=
=
=

enc28j60Read(MAADR5);
enc28j60Read(MAADR4);
enc28j60Read(MAADR3);
enc28j60Read(MAADR2);
enc28j60Read(MAADR1);
enc28j60Read(MAADR0);


}
//------------------------------------------------------------------------------------//Ham set dia chi MAC (dang luu trong buffer macaddr[6] xuong chip ethernet
void ethSetMacAddress(unsigned char* macaddr)
{
enc28j60Write(MAADR5, *macaddr++);
enc28j60Write(MAADR4, *macaddr++);
enc28j60Write(MAADR3, *macaddr++);
enc28j60Write(MAADR2, *macaddr++);
enc28j60Write(MAADR1, *macaddr++);
enc28j60Write(MAADR0, *macaddr++);
}
//------------------------------------------------------------------------------------//Ham tra lai con tro den buffer ethernet (tren RAM cua CPU)
unsigned char* ethGetBuffer(void)
{
return ethBuffer;
}

Nhắc lại là để truy xuất được đến các hàm trong module “enc28j60.c”, đến đây các bạn nhất thiết
phải thêm các khai báo hàm đã viết trong “enc28j60.c” vào file header “enc28j60.h”. Tức là
trong file “enc28j60.h” phải có các dòng khai báo này:
Code:
//---------------------------------------------------------------------------unsigned char enc28j60SPIRead(unsigned char op, unsigned char address);
void enc28j60SPIWrite(unsigned char op, unsigned char address, unsigned char
data);
void enc28j60SetBank(unsigned char address);
void enc28j60ReadBuffer(unsigned int len, unsigned char* data);
void enc28j60WriteBuffer(unsigned int len, unsigned char* data);
unsigned char enc28j60Read(unsigned char address);
void enc28j60Write(unsigned char address, unsigned char data);
unsigned int enc28j60PhyRead(unsigned char address);

void enc28j60PhyWrite(unsigned char address, unsigned int data);
void enc28j60PacketSend(unsigned int len, unsigned char* packet);
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char*
packet);
void delay_us(unsigned short time_us);
void enc28j60Init(void);

Tiếp theo ta viết phần xử lý khi ENC28J60 nhận được 1 frame dữ liệu hợp lệ.
Như đã nói trước đó, khi ENC28J60 nhận được 1 frame dữ liệu hợp lệ, nó sẽ tạo 1 ngắt để báo
cho VĐK biết và xử lý dữ liệu nhận được này. Như vậy ta cần viết một ISR ngắt tương ứng cho
sự kiện đó:
Code:
//------------------------------------------------------------------------------------//Vector ngat cua ethernet, mot ngat ngoai se duoc khoi tao boi chip ethernet
// moi khi no nhan duoc 1 frame ethernet (dung dia chi cua no)


ISR (ETH_INTERRUPT)
{
eth_got_frame = 1;
time_watchdog = 0;
ETH_INT_DISABLE;
}

Trong hàm này ta có sử dụng định nghĩa vector ngắt (ISR), do đó ta cần include file tương ứng
vào. Thêm dòng này vào đầu file "ehternet.c"
Code:
#include <avr/interrupt.h>

Trong hàm này, ta sử dụng 2 biến toàn cục là eth_got_frame và time_watchdog, hai biến này cần
được khai báo trong file “enc28j60.c” (nên để ở dầu file)

Code:
unsigned char eth_got_frame = 0;
volatile unsigned int time_watchdog;

Giải thích: thay vì viết toàn bộ phần xử lý trong hàm ngắt (ISR), trong hàm ngắt ta chỉ đơn giản
là set biến eth_got_frame = 1 để báo cho biết có 1 frame đang chờ xử lý, biến này
(eth_got_frame) sẽ liên tục được kiểm tra bởi hàm dịch vụ ethernet (ethService) mà ta sẽ viết sau
đây, hàm này (ethService) sẽ liên tục được gọi trong 1 vòng lặp ở chương trình chính (hàm main
trong file “ntAVRnet.c”) để thực thi liên tục.
Cách viết này nhằm tránh xảy ra hiện tượng ngắt chồng ngắt, có thể dẫn đến chương trình thực
thi không đúng mong muốn do nội dung hàm ngắt quá dài, chưa thực thi xong đã xảy ra 1 ngắt
khác.
Còn biến time_watchdog là một biến nhằm phát hiện các lỗi dẫn đến treo các giao thức mạng
hoặc IC ENC28J60. Biến này sẽ được tăng liên tục bởi timer nhưng lại được reset về 0 mỗi khi
nhận được 1 frame ethernet mới, điều này cho phép phát hiện 1 khoảng thời gian quá lâu mà ta
không nhận được frame ethernet nào (khi biến time_watchdog tăng đến một giá trị ngưỡng), khi
đó ta giả thiết là ENC28J60 bị treo hay phát sinh lỗi, lúc đó ta sẽ gọi hàm reset IC này và khởi
động lại giao thức ethernet. Điều này trong thực tế rất hữu ích, nó giúp mạch của chúng ta chạy
ổn định hơn rất nhiều.
Đồng thời ta cũng disable ngắt ngoài này trong thời gian chờ xử lý frame.
Vậy ta viết tiếp phần xử lý frame ethernet trong hàm ethService như sau:
Code:

//------------------------------------------------------------------------------------//Ham duoc goi lien tuc de thuc thi cac tac vu cua giao thuc ethernet
void ethService(void)
{
int len;
struct ntEthHeader* ethPacket;
if(!eth_got_frame) return;
eth_got_frame = 0;

// look for a packet
len = ethGetFrame(ETHERNET_BUFFER_SIZE, ethBuffer);
if(len)
{

ethPacket = (struct ntEthHeader*)ðBuffer[0];
#ifdef ETH_DEBUG


printf("Received packet len: %d, type:", len);
#endif
if(ethPacket->type == HTONS(ETH_TYPE_IP))
//Neu day la frame danh cho giao thuc IP
{
#ifdef ETH_DEBUG
printf("IP packet\r\n");
#endif
arpIPPacketIn((unsigned char*)ðBuffer[0]);
IPProcess( len-ETH_HEADER_LEN, (struct
ntIPHeader*)ðBuffer[ETH_HEADER_LEN] );
}
else if(ethPacket->type == HTONS(ETH_TYPE_ARP))
//Neu day la 1 frame cua giao thuc ARP
{
#ifdef ETH_DEBUG
printf("ARP packet\r\n");
#endif
arpArpProcess(len, ethBuffer );
}else{
#ifdef ETH_DEBUG

printf("Unknown packet:%x\r\n",ethPacket->type);
#endif
ethInit();
}
ETH_INT_ENABLE;
}
return;
}
//-------------------------------------------------------------------------------------

Giải thích:
Hàm này khi phát hiện có frame mới (biến eth_got_frame khác 0) thì tiến hành kiểm tra trường
Type trong header frame ethernet để xem dữ liệu chứa trong frame là của giao thức nào (IP hay
ARP) và sẽ gọi hàm tương ứng của giao thức đó để xử lý.
Có một lưu ý quan trọng là trong trình biên dịch gcc (cũng như các trình biên dịch ngôn ngữ C
khác cho AVR), đối với các biến có kích thước lớn hơn 1 byte (int, double, long,…) thì thứ tự các
byte trong bộ nhớ của AVR được sắp xếp theo thứ tự ngược lại với thứ tự trong các header của
gói tin (frame ethernet, IP packet,…). Do đó khi đọc các biến này ra từ buffer ethernet cũng như
trước khi ghi vào buffer, ta phải đổi thứ tự các byte này. Ta viết một số macro cho mục đích này
và lưu luôn trong file “packet.h” để sử dụng sau này.
Code:
//------------------------------------------------------------------------------------#define HTONS(s)
((s<<8) | (s>>8))
//danh cho bien 2 byte
#define HTONL(l)
((l<<24) | ((l&0x00FF0000l)>>8) |
((l&0x0000FF00l)<<8) | (l>>24))
//danh cho bien 4 byte
//


Lưu ý là trong hàm này ta có gọi các hàm của giao thức lớp trên (IP và ARP). Đó là các hàm
arpIPPacketIn, IPProcess, và arpArpProcess. Các hàm này ta vẫn chưa viết nên khi biên dịch file
“ethernet.c” sẽ có báo lỗi thiếu các hàm này.
Để cho mục đích debug sau này, trong hàm ta có sử dụng hàm xuất ra cổng serial là printf. Hàm


này các bạn tự viết lấy dùng uart nhé. Trong nội dung hướng dẫn này tập trung vào giao thức
TCP/IP thôi.
Sau khi xử lý xong frame, ta cần enable ngắt ngoài trở lại.
Đến đây là cơ bản xong giao thức ethernet, tiếp theo sẽ là giao thức IP và ARP, rắc rối hơn nhiều.


Bài 5: Lập trình cho giao thức IP (Internet Protocol) và giao thức ARP
(Address Réolution Protocol)
Trước hết, ta cần tìm hiểu về hoạt động của giao thức IP.
(phần này viết một cách đơn giản và dễ hiểu nhất cho dân điện tử, không chuyên về network có
thể hiểu phần nào hoạt động của các giao thức để có thể hiểu code và tự viết code, dân IT vào
đọc đừng cười nhé)
Cách thức mà dữ liệu được gửi qua giao thức IP được tiến hành như sau:
- Khi nhận được 1 segment dữ liệu (từ giao thức lớp trên là TCP hay UDP) cần gửi đến đích nào
đó, địa chỉ đích này phải được xác định bằng địa chỉ IP (tức là địa chỉ mạng hay địa chỉ luận lý).
Lớp giao thức IP sẽ gắn thêm vào đầu segment dữ liệu một header IP để tạo thành gói IP hoàn
chỉnh. Trong header IP này có chứa 2 thông tin quan trọng, đó là địa chỉ host gửi (source IP
address) và địa chỉ host nhận (destination IP address). Địa chỉ source đương nhiên là địa chỉ của
bản thân nó, còn địa chỉ đích phải được cung cấp cho lớp IP khi muốn gửi dữ liệu qua giao thức
này.
- Gói tin IP này sau đó được chuyển đến lớp giao thức ethernet để thêm phần header ethernet vào
và gửi đi.
- Nhưng như ở phần trước ta đã biết, giao thức ethernet lại gửi các frame dữ liệu đi dựa vào 1
loại địa chỉ khác là địa chỉ MAC (hay còn gọi là địa chỉ vật lý). Tại sao lại cần đến 2 địa chỉ như

vậy? Lý do là địa chỉ vật lý chỉ có giá trị trong phạm vi mạng LAN, nó sẽ không thể giúp xác
định vị trí host ở bên ngoài phạm vi mạng LAN. Khi gửi dữ liệu ra ngoài mạng LAN, các router
sẽ chuyển dữ liệu đi dựa và địa chỉ IP.
- Như vậy trong phần địa chỉ MAC nguồn và địa chỉ MAC đích trong header của frame ehternet,
ta sẽ điền các địa chỉ nào? Đối với địa chỉ MAC nguồn, đương nhiên ta sẽ điền địa chỉ MAC của
chính ENC28J60 đã được xác lập. Nhưng còn địa chỉ MAC đích, sẽ có 2 trường hợp xảy ra:
+ Nếu host đích nằm trong cùng 1 mạng LAN với chúng ta, ta sẽ điền địa chỉ MAC đích là địa
chỉ tương ứng của host đích. Frame dữ liệu sẽ được gửi thẳng đến đích.
+ Nếu host đích nằm bên ngoài mạng LAN, rõ ràng ta không thể gửi dữ liệu trực tiếp đến host
đích mà phải thông qua gateway, khi đó địa chỉ MAC đích phải là địa chỉ gateway. (Để dễ hiểu,
cứ hình dung ta gắn mạch này tại nhà, sau modem ADSL cùng với 1 máy tính để bàn tại nhà, nếu
mạch của chúng ta cần gửi dữ liệu đến máy tính cũng ở tại nhà, trong cùng mạng LAN, nó sẽ gửi
trực tiếp theo địa chỉ MAC của máy tính đó. Nhưng nếu cần gửi dũ liệu đến 1 máy tính bên
ngoài, nằm trên mạng Internet, khi đó nó không thể gửi frame dữ liệu thẳng đến máy tính kia mà
nó phải gửi qua gateway, trong trường hợp này chính là modem ADSL. Như vậy lúc đó địa chỉ
MAC đích phải là địa chỉ MAC của gateway).
- Vẫn còn một vấn đề nữa mà ta phải giải quyết. Đó là trong cả hai trường hợp trên, dù là cần gửi
cho gateway hay thẳng đến host đích, thì đến đây, ta mới chỉ biết địa chỉ IP của host đích (hay
của gateway) mà không biết địa chỉ MAC tương ứng. Vậy nảy sinh một vấn đề là làm sao biết
được địa chỉ MAC của một host khi biết địa chỉ IP?


Đến đây, chính là phát sinh vai trò của giao thức phân giải địa chỉ (APR – Address Resolution
Protocol). Vai trò của giao thức này là tìm ra địa chỉ MAC khi biết địa chỉ IP của 1 host.
Hoạt động của giao thức ARP:
- Cách thức làm việc của giao thức ARP thực ra khá đơn giản. Nhiệm vụ của nó là khi giao thức
IP hỏi nó: “Host có địa chỉ IP là a.b.c.d thì địa chỉ MAC là bao nhiêu?” thì nó phải trả lời ngay:
“Địa chỉ MAC của nó là XX:XX:XX:XX:XX:XX!”. Chức năng này trong project của chúng ta
sẽ được cung cấp bởi hàm “arpIpOut” (xem lại lưu đồ dữ liệu vào ra). Tức là trước khi giao thức
IP chuyển dữ liệu xuống cho giao thức ethernet, nó sẽ gọi hàm “arpIpOut” để phân giải địa chỉ

MAC cho host đích.
- Tuy nhiên chỉ chừng đó chưa đủ giải thích cho hoạt động của ARP. Câu hỏi tiếp theo sẽ là: Vậy
ARP lấy thông tin ở đâu để trả lời cho câu hỏi trên?
- Cách thức nó giải quyết vấn đề cũng đơn giản không kém: giao thức ARP duy trì một bảng gọi
là ARP cache gồm 2 cột, một cột ghi địa chỉ IP, một cột ghi địa chỉ MAC tương ứng với địa chỉ
IP đó. Mỗi khi được hỏi bởi giao thức IP, nó sẽ tra bảng này để tìm câu trả lời.
- Vậy đến đây, các bạn phải nảy ra ngay 1 câu hỏi kế tiếp: vậy những gì chứa trong bảng ARP
cache từ đâu mà có, khi mới khởi động hệ thống, bảng này đương nhiên sẽ trống trơn. Và chuyện
gì sẽ xảy ra khi giao thức ARP được hỏi về 1 địa chỉ IP, mà khi nó tra trong bảng ARP thì không
thấy?
- Cách giải quyết của giao thức ARP như sau: khi được hỏi về một địa chỉ IP a.b.c.d nào đó mà
không có sẵn trong bảng ARP cache, nó sẽ lập tức “la lớn” trong mạng LAN: “Ai là người có địa
chỉ IP là a.b.c.d?” Các máy tính trong mạng LAN đều nghe được câu hỏi này, và lẽ dĩ nhiên chỉ
có đúng máy tính có địa chỉ IP a.b.c.d sẽ trả lời: “Là tôi đây, tôi có địa chỉ MAC là
XX:XX:XX:XX:XX:XX!”. Vậy giao thức ARP sẽ lập tức thêm cặp địa chỉ IP a.b.c.d và địa chỉ
MAC XX:XX:XX:XX:XX:XX vào trong bảng ARP cache và trả lời lại cho giao thức IP: “Địa
chỉ MAC của nó là XX:XX:XX:XX:XX:XX!”.
- Nghe có vẻ buồn cười nhưng trong thực tế, trên máy tính của chúng ta, mọi việc diễn ra đúng
như vậy, việc “la lớn” của ARP được thực hiện bằng cách nó gửi đi một gói tin có tên gọi là ARP
request dưới dạng broadcast, tức là gửi đến mọi máy trong mạng LAN, địa chỉ MAC đích của
gói broadcast sẽ là FF:FF:FF:FF:FF:FF. Trong gói ARP request có chứa địa chỉ IP mà nó cần tìm.
Tất cả các máy tính trong mạng LAN sẽ nhận được gói tin này, và máy tính có địa chỉ IP trên sẽ
trả lời bằng bản tin ARP reply, trong bản tin này sẽ có địa chỉ MAC của nó.
- Đó là cách thứ nhất để giao thức ARP điền thông tin vào bảng ARP cache. Còn có 1 cách nữa
khá đơn giản giúp nó điền đầy thông tin vào bảng ARP cache: đó là mỗi khi có 1 gói tin IP đến,
lẽ dĩ nhiên là phía host đã gửi gói tin này đã điền đầy đủ thông tin địa chỉ MAC (chứa trong
header ehternet) và địa chỉ IP của nó (chứa trong header IP). Như vậy giao thức ARP sẽ lấy cặp
địa chỉ này và cập nhật vào bảng ARP cache.
- Điều cuối cùng cần lưu ý về bảng ARP cache này là các dòng (tức cặp địa chỉ IP – MAC) chứa
trong nó không được duy trì mãi mãi mà có 1 thời gian timeout, quá thời gian này mà không có



×