184
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

85012978-TLTK-AVRwebserver

  • Upload
    572460

  • View
    107

  • Download
    2

Embed Size (px)

Citation preview

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 tutTạo các file “enc28j60.c”, “enc28j60.h” và “enc28j60conf.h”. Add vào projectENC28J60 đượ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 EDMADSTH (0x15|0x00)#define EDMACSL (0x16|0x00)#define EDMACSH (0x17|0x00)// Bank 1 registers#define EHT0 (0x00|0x20)#define EHT1 (0x01|0x20)#define EHT2 (0x02|0x20)#define EHT3 (0x03|0x20)#define EHT4 (0x04|0x20)#define EHT5 (0x05|0x20)#define EHT6 (0x06|0x20)

#define EHT7 (0x07|0x20)#define EPMM0 (0x08|0x20)#define EPMM1 (0x09|0x20)#define EPMM2 (0x0A|0x20)#define EPMM3 (0x0B|0x20)#define EPMM4 (0x0C|0x20)#define EPMM5 (0x0D|0x20)#define EPMM6 (0x0E|0x20)#define EPMM7 (0x0F|0x20)#define EPMCSL (0x10|0x20)#define EPMCSH (0x11|0x20)#define EPMOL (0x14|0x20)#define EPMOH (0x15|0x20)#define EWOLIE (0x16|0x20)#define EWOLIR (0x17|0x20)#define ERXFCON (0x18|0x20)#define EPKTCNT (0x19|0x20)// Bank 2 registers#define MACON1 (0x00|0x40|0x80)#define MACON2 (0x01|0x40|0x80)#define MACON3 (0x02|0x40|0x80)#define MACON4 (0x03|0x40|0x80)#define MABBIPG (0x04|0x40|0x80)#define MAIPGL (0x06|0x40|0x80)#define MAIPGH (0x07|0x40|0x80)#define MACLCON1 (0x08|0x40|0x80)#define MACLCON2 (0x09|0x40|0x80)#define MAMXFLL (0x0A|0x40|0x80)#define MAMXFLH (0x0B|0x40|0x80)#define MAPHSUP (0x0D|0x40|0x80)#define MICON (0x11|0x40|0x80)#define MICMD (0x12|0x40|0x80)#define MIREGADR (0x14|0x40|0x80)#define MIWRL (0x16|0x40|0x80)#define MIWRH (0x17|0x40|0x80)#define MIRDL (0x18|0x40|0x80)#define MIRDH (0x19|0x40|0x80)// Bank 3 registers#define MAADR1 (0x00|0x60|0x80)#define MAADR0 (0x01|0x60|0x80)#define MAADR3 (0x02|0x60|0x80)#define MAADR2 (0x03|0x60|0x80)#define MAADR5 (0x04|0x60|0x80)#define MAADR4 (0x05|0x60|0x80)#define EBSTSD (0x06|0x60)#define EBSTCON (0x07|0x60)#define EBSTCSL (0x08|0x60)#define EBSTCSH (0x09|0x60)#define MISTAT (0x0A|0x60|0x80)#define EREVID (0x12|0x60)#define ECOCON (0x15|0x60)#define EFLOCON (0x17|0x60)#define EPAUSL (0x18|0x60)#define EPAUSH (0x19|0x60)// PHY registers#define PHCON1 0x00#define PHSTAT1 0x01

#define PHHID1 0x02#define PHHID2 0x03#define PHCON2 0x10#define PHSTAT2 0x11#define PHIE 0x12#define PHIR 0x13#define PHLCON 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 0x40#define ENC28J60_WRITE_BUF_MEM 0x7A#define ENC28J60_BIT_FIELD_SET 0x80#define ENC28J60_BIT_FIELD_CLR 0xA0#define ENC28J60_SOFT_RESET 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 TXSTART_INIT 0x0000 //Dia chi bat dau buffer gui#define TXSTOP_INIT 0x05FF //Dia chi ket thuc buffer gui#define RXSTART_INIT 0x0600 //Dia chi bat dau buffer nhan#define RXSTOP_INIT 0x1FFF //Dia 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<<ENC28J60_CONTROL_CS);#define ENC28J60_CS_HI() ENC28J60_CONTROL_PORT |=

(1<<ENC28J60_CONTROL_CS);//#define ETH_INTERRUPT INT2_vect//#if defined (__AVR_ATmega32__)

#define ETH_INT_ENABLE GICR |= (1<<INT2)#define ETH_INT_DISABLE GICR &= ~(1<<INT2)

#endif

#if defined (__AVR_ATmega644__) || defined (__AVR_ATmega644P__)#define ETH_INT_ENABLE EIMSK |= (1<<INT2)#define ETH_INT_DISABLE EIMSK &= ~(1<<INT2)

#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<<SPIF))); SPDR = 0x00; while(!(SPSR & (1<<SPIF))); if(address & 0x80){

SPDR = 0x00;while(!((SPSR) & (1<<SPIF)));

}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<<SPIF)));SPDR = data;while(!(SPSR & (1<<SPIF)));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<<SPIF)));while(len--){

SPDR = 0x00;while(!(SPSR & (1<<SPIF)));*data++ = SPDR;

}ENC28J60_CS_HI();

}void enc28j60WriteBuffer(unsigned int len, unsigned char* data){

ENC28J60_CS_LO();SPDR = ENC28J60_WRITE_BUF_MEM;while(!(SPSR & (1<<SPIF)));while(len--){

SPDR = *data++;while(!(SPSR & (1<<SPIF)));

}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 ENC28J60Tiế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 = ((len<maxlen)?(len) : (maxlen));enc28j60ReadBuffer(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 loETXSTH, HI8(TXSTART_INIT), //start hiETXNDL, LO8(TXSTOP_INIT ), //end loETXNDH, HI8(TXSTOP_INIT ), //end hi

ERXSTL, LO8(RXSTART_INIT), //start loERXSTH, HI8(RXSTART_INIT), //start hiERXNDL, LO8(RXSTOP_INIT ), //end loERXNDH, HI8(RXSTOP_INIT ), //end hi

MACON2, 0x00,MACON1, (MACON1_MARXEN | MACON1_RXPAUS | MACON1_TXPAUS),MACON3, ( MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN),MAMXFLL, LO8(1518),MAMXFLH, HI8(1518),MABBIPG, 0x12, //half duplexMAIPGL, 0x12,MAIPGH, 0x0C, //half duplex

MAADR5, ENC28J60_MAC0,MAADR4, ENC28J60_MAC1,MAADR3, ENC28J60_MAC2,MAADR2, ENC28J60_MAC3,

MAADR1, ENC28J60_MAC4,MAADR0, 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;

delay_loops = (time_us+3)/5*CYCLES_PER_US; // +3 for rounding up (dirty)

// 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_CONTROL_CS);ENC28J60_CS_HI();ENC28J60_SPI_PORT |= (1<<ENC28J60_SPI_SCK); //sck = hi

ENC28J60_SPI_DDR |= (1<<ENC28J60_SPI_SS)|(1<<ENC28J60_SPI_MOSI)|(1<<ENC28J60_SPI_SCK); //SS,MOSI,SCK = OUT

ENC28J60_SPI_DDR &= ~(1<<ENC28J60_SPI_MISO); //MISO = INSPCR = (0<<SPIE)|(1<<SPE)|(0<<DORD)|(1<<MSTR)|(0<<CPOL)|(0<<CPHA)|

(0<<SPR1)|(0<<SPR0);SPSR = (1<<SPI2X);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_config[i+1]));

}enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS); //=no loopback of transmitted

framesenc28j60SPIWrite(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 giaJan 2010

Bài viết60

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<<SPIF)));

SPDR = 0x00;while(!(SPSR & (1<<SPIF)));if(address & 0x80){SPDR = 0x00;while(!((SPSR) & (1<<SPIF)));}res = SPDR;ENC28J60_CS_HI();return res;}

Bài 4: Lập trình giao thức ethernetHOẠ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 14struct 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 Taggingstruct 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 Taggingstruct 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 Headerstruct 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ậnkhi 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.cCode://----------------------------------------------------------------------------// 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.hCode://----------------------------------------------------------------------------// 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 MTU_SIZE

#define MTU_SIZE 1460#endif#ifndef IP_HEADER_LEN

#define IP_HEADER_LEN 20#endif //IP_HEADER_LEN#ifndef TCP_HEADER_LEN#define TCP_HEADER_LEN 20#endif //TCP_HEADER_LEN//#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 Ethernetvoid ethInit(void){

enc28j60Init();ETH_INT_ENABLE;

}//--------------------------------------------------------------------------------------

//Ham goi 1 frame xuong chip ethernetvoid ethSendFrame(unsigned int len, unsigned char* packet){

enc28j60PacketSend(len, packet);}//--------------------------------------------------------------------------------------//Ham doc 1 frame ethernet tu chip ethernet ve buffer tren RAM cua CPUunsigned 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++ = enc28j60Read(MAADR5);*macaddr++ = enc28j60Read(MAADR4);*macaddr++ = enc28j60Read(MAADR3);*macaddr++ = enc28j60Read(MAADR2);*macaddr++ = enc28j60Read(MAADR1);*macaddr++ = enc28j60Read(MAADR0);

}//--------------------------------------------------------------------------------------//Ham set dia chi MAC (dang luu trong buffer macaddr[6] xuong chip ethernetvoid 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 ethernetvoid ethService(void){

int len;struct ntEthHeader* ethPacket;if(!eth_got_frame) return;

eth_got_frame = 0;// look for a packetlen = ethGetFrame(ETHERNET_BUFFER_SIZE, ethBuffer);

if(len){

ethPacket = (struct ntEthHeader*)&ethBuffer[0];

#ifdef ETH_DEBUGprintf("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_DEBUGprintf("IP packet\r\n");#endifarpIPPacketIn((unsigned char*)&ethBuffer[0]);IPProcess( len-ETH_HEADER_LEN, (struct

ntIPHeader*)&ethBuffer[ETH_HEADER_LEN] );}else if(ethPacket->type == HTONS(ETH_TYPE_ARP))//Neu day la 1 frame cua giao thuc ARP{

#ifdef ETH_DEBUGprintf("ARP packet\r\n");#endifarpArpProcess(len, ethBuffer );

}else{#ifdef ETH_DEBUGprintf("Unknown packet:%x\r\n",ethPacket->type);#endifethInit();

}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ó thông tin cập nhật cho cặp địa chỉ đó thì nó sẽ bị xóa khỏi ARP cache, và nếu lỡ giao thức IP cần gửi dữ liệu cho địa chỉ IP đã bị xóa thì ARP sẽ đi hỏi lại về địa chỉ IP đó.

Note: để xem được bảng arp cache trên máy tính của mình, các bạn có thể mở cửa sổ command (vào Start->Run->gõ cmd, nhấn Enter), sau đó gõ lệnh "arp -a".

Bây giờ ta bắt tay vào viết code:Việc trước tiên phải làm là khai báo cấu trúc header IP và cấu trúc gói ARP trong file “packet.h”.Cấu trúc của gói IP như sau:

- Ý nghĩa các field trong header IP:+ Version (có chiều dài 4 bit): cho biết phiên bản của giao thức, đối với trường hợp của chúng ta, giao thức là IP version 4, trường này sẽ luôn có giá trị là 4 (0100)+ Header Length (4 bit): cho biết chiều dài của header IP, tính theo đơn vị 4 byte (32 bit)+ TOS (8 bit): Type of Service+ Total Length (16 bit): 16 bit tổng chiều dài của gói IP gồm cả phần header+ Identification (16 bit): dùng nhận diện các phân đoạn của gói IP+ Flags: gồm 3 bit. Bit đầu tiên: không sử dụng. Bit 2: DF (Don’t Fragment) = 1 có nghĩa là không phân đoạn gói này. Bit 3: MF (More Fragment) = 0 => đây là phân đoạn cuối cùng+ Fragmented offset (13 bit): độ dời (đơn vị 8 byte) tính từ điểm bắt đầu của Header tới điểm bắt đầu của phân đoạn(3 trường trên: Identification, Flags, Fragmented offset dùng cho trường hợp đặc biệt khi ta cần chia đoạn dữ liệu ban đầu thành nhiều phân đoạn, đóng gói trong các gói tin nhỏ hơn, khi đó ta cần dùng các trường này cho mục đích ráp lại các phân đoạn để khôi phục lại đoạn dữ liệu ban đầu, trong project của chúng ta sẽ không có xử lý trường hợp này).+ TTL (Time to Live) (8 bit): thời gian tồn tại trên mạng hoặc số chặng trên mạng mà gói đi qua trước khi bị hủy bỏ+ Protocol (8 bit): nhận diện Protocol trên lớp IP+ Header checksum (16 bit): sửa sai cho phần Header+ Các vùng địa chỉ nguồn, địa chỉ đích: địa chỉ IP 32 bit+ Option: các tùy chọn dùng cho việc kiểm tra: Loose source routing, Strict source routing, Record route và Timestamp + Padding: Gồm các số zero được thêm vào sao cho chiều dài của vùng Header là bội số của 32 bit(Trong phạm vi project của chúng ta, sẽ không có phần option và padding)Vậy ta khai báo trong file “packet.h” nội dung như sau:

Code://--------------------------------------------------------------------------------------//Cau truc IP headerstruct ntIPHeader{

unsigned char verHdrLen;unsigned char ToS;unsigned int Len;unsigned int IDNumber;unsigned int Offset;unsigned char TTL;unsigned char Protocol;unsigned int Checksum;unsigned long srcIPAddr;unsigned long desIPAddr;unsigned char Option[4];

};#define IP_HEADER_LEN 20

#define IP_PROTO_ICMP 1#define IP_PROTO_TCP 6#define IP_PROTO_UDP 17

Còn đây là cấu trúc gói ARP:

- Ý nghĩa các trường: + Hardware type (2 bytes): cho biết loại địa chỉ phần cứng, đối với địa chỉ MAC của giao thức ethernet thì giá trị này được qui định là "0x0001".+ Protocol type (2 bytes): cho biết loại địa chỉ giao thức lớp trên, đối với địa chỉ IP, giá trị này được qui định là “0x0800”.+ HLEN (1 byte): cho biết chiều dài của địa chỉ vật lý (địa chỉ MAC).+ PLEN (1 byte): cho biết chiều dài của địa chỉ giao thức (địa chỉ IP)+ Operation (2 bytes): cho biết hoạt động đang thực hiện trong gói tin này (request hay reply).+ Sender H/W (hardware address, 6 bytes): địa chỉ vật lý của phía gửi.+ Sender IP (4 bytes): địa chỉ IP của phía gửi.+ Target H/W (6 bytes): địa chỉ vật lý của phía nhận, nếu chưa biết thì sẽ là chứa toàn 0.

+ Target IP (4 bytes): địa chỉ IP của phía nhận.Vậy ta khai báo cấu trúc gói ARP trong file “packet.h” như sau:Code://--------------------------------------------------------------------------------------//Cau truc ARP headerstruct ntARPHeader{

unsigned int hwType;unsigned int protocol;unsigned char hwLen;unsigned char protoLen;unsigned int opcode;struct ntEthAddr shwaddr;unsigned long sipaddr;struct ntEthAddr dhwaddr;unsigned long dipaddr;

};#define ARP_OPCODE_REQUEST 1#define ARP_OPCODE_REPLY 2#define ARP_HWTYPE_ETH 1

Không có thời gian để viết chi tiết hơn. Bạn nào đọc thấy chỗ nào khó hiểu thì cứ hỏi nhé.

Dù là trên PIC hay AVR hay bất cứ micro controller nào thì cơ chế cũng như nhau thôi (thậm chí trên máy tính thì cũng tương tự), chỉ khác là đối với PIC thì Microchip đã cung cấp đầy đủ bộ thư viện cho các giao thức ethernet, ip, arp, udp, tcp, dhcp, http, không cần phải viết lại như anh đang làm cho AVR. Cơ chế như sau:- Khi ta mở máy tính lên, mở trình duyệt và gõ vào địa chỉ của webserver (PIC hay AVR gì cũng thế), giả sử ở đây ta gõ vào địa chỉ IP như sau: http://192.168.1.10- Sau khi nhấn enter thì máy tính của chúng ta sẽ gửi đi một bản tin request của giao thức HTTP (Hyper Text Transfer Protocol, giao thức để truyền/nhận nội dung trang web), thường là HTTP Get thông qua giao thức TCP (với cổng TCP được qui định cho giao thức HTTP là 80) đến địa chỉ webserver trên.- Webserver, ở đây chính là vi điều khiển của chúng ta (PIC hay AVR) nhận được bản tin này (tất nhiên bản tin này sẽ đi qua hết các lớp giao thức ethernet, IP, TCP rồi mới đến HTTP). Tại đây vi điều khiển sẽ đọc và phân tích bản tin HTTP request này để biết máy tính đang yêu cầu tải nội dung trang web nào.- Sau đó vi điều khiển sẽ lấy nội dung trang web này (được soạn thảo theo ngôn ngữ HTML) chứa trên trên flash ROM, nó cũng có thể thêm vào trang web đó một số thông tin (ví dụ đọc giá trị từ các sensor cảm biến nhiệt độ và đưa vào trong trang web), và gửi toàn bộ nội dung trang web thông qua giao thức TCP trở lại cho máy tính. Nếu nội dung trang web lớn nó có thể được gửi đi trên rất nhiều gói tin, vì mỗi gói tin chỉ chứa tối đa 1460 byte dữ liệu mà thôi.- Máy tính nhận nội dung trang web và trình duyệt sẽ hiển thị lên cho chúng ta thấy.- Để điều khiển board từ xa qua web, trên trang web ta có thể thiết kế một nút nhấn chẳng hạn. Khi ta nhấn nút này trên trình duyệt, máy tính sẽ gửi đi một bản tin HTTP nữa là HTTP Post, trong bản tin này sẽ chứa các thông tin về trạng thái các nút option hay các giá trị trong các ô edit text có trên trang web.- Vi điều khiển sẽ nhận bản tin HTTP post này, phân tích dữ liệu chứa trong đó để có đáp ứng tương ứng

(bật tắt relay chẳng hạn) sau đó nó sẽ gửi trả lại lần nữa nội dung trang web đã cập nhật những thay đổi vừa rồi (ví dụ bật reley thì trên web sẽ có 1 hình tròn đổi sang màu đỏ chẳng hạn). Trình duyệt sẽ update nội dung này lên và ta sẽ thấy được tác động của thao tác điều khiển đó.Từ từ anh sẽ viết và giải thích tất cả các hoạt động đó trên AVR, trên PIC cũng thế nhưng các hàm được Microchip cung cấp sẵn mà thôi.

Thầy cho em hỏi chỗ này, vì vừa đọc em vừa tưởng tượng thức thế cho dễ hiểu.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.Câu hỏi:Số host truyền nhận trong trường hợp này là bao nhiêu : 2 hay >2 host ? Vì em nghĩ 2 host là TH riêng, 1 host truyền 1 host nhận, thì host nhận check IP là chính nó.Nếu host nhận đc frame mà nó kiểm tra ko phải IP add(dst) của nó thì nó sẽ xử lý như thế nào, có phải lúc này IP add(dst) là IP broadcast?Và sau đó tới một host nào đó, check IP add là của chính nó thì TH1 được xảy ra, phải ko ạ?

Em liên tưởng TCP/IP lúc này xử lý IP như một Router check IP cho cả 1 mạng LAN, ...kết nối với nó. Nếu frame được gửi cho tất cả máy tính trong LAN, thì sẽ ko có host đích cụ thể nào ạ?Nếu với HTTP, hay 1 ứng dụng nào khác thì điều này có thể được, nhưng với các ứng Email chẳng hạn, cần tính bảo mật riêng tư thì IPbroadcast lại ko được- do các máy đều nhận đc frame để xử lí.EM có vài câu, ko biết đúng sai thế nào?Câu hỏi của em thực ra rất hay, vấn đề này tôi đã không giải thích chi tiết trong phần giao thức ethernet.- Giao thức ethernet chỉ hỗ trợ 2 hình thức truyền: unicast: tức là 1 host gửi, 1 host nhận; và broadcast, tức là 1 host gửi, tất cả các host trong mạng LAN đều nhận.

- Trong mạng LAN cơ bản, sử dụng 1 HUB để kết nối các máy tính với nhau trong mạng LAN. Khi một máy tính gửi đi một frame ethernet, bất kể là nó gửi unicast hay broadcast, thì tất cả các máy tính trong mạng LAN đó (kết nối với HUB) đều nhận được frame đó. Nhưng mỗi máy tính sẽ đối chiếu địa chỉ MAC nhận với địa chỉ của chính nó (ở đây chỉ kiểm tra địa chỉ MAC, không kiểm tra địa chỉ IP, khi lên giao thức IP thì mới kiểm tra địa chỉ IP), và các host sẽ chỉ nhận frame và chuyển lên giao thức IP trong 2 trường hợp:+ Hoặc địa chỉ là của chính nó+ Hoặc địa chỉ là broadcast- Vậy điều gì sẽ xảy ra nếu một máy tính nào đó phá vỡ luật chơi, nhận dữ liệu không phải dành cho nó, như vậy phải chăng nó có thể nghe trộm dữ liệu gửi cho máy tính khác.- Câu trả lời ở đây là: đúng như vậy, mạng LAN kiểu này hoàn toàn không có bảo mật, với các phần mềm nghe trộm dữ liệu, một máy tính có thể nghe trộm dữ liệu của các máy khác trong cùng mạng LAN.- Ở đây thiết bị HUB dùng để kết nối các máy tính tạo thành mạng LAN chỉ đơn giản là khi nhận

được dữ liệu đến 1 port của nó, nó sẽ khuyếch đại và phát ra lại ở tất cả các port, để tất cả máy tính nối đến nó đều nhận được dữ liệu.[Flash]http://www.shareswf.com/media/games/swf/16193.swf[/Flash][flash=300,200]http://www.shareswf.com/media/games/swf/16193.swf[/flash][FLASH=300,300]http://www.shareswf.com/media/games/swf/16193.swf[/FLASH]- Tất nhiên người ta cũng nhận ra hạn chế đó của HUB, cũng như một số hạn chế khác, và phát triển một thiết bị "thông minh" hơn HUB, đó là SWITCH.- Về hình thức, SWITCH cũng có nhiều port và dùng để kết nối các máy tính tạo thành mạng LAN giống HUB, tuy nhiên sự khác biệt là: mỗi khi nhận được dữ liệu đến 1 port của nó, SWITCH sẽ kiểm tra địa chỉ MAC đích trên frame, sau đó nó tìm xem máy tính có địa chỉ MAC tương ứng đang nằm ở port nào của nó, và chuyển dữ liệu đến port đó. Như vậy chỉ có máy tính đó nhận được dữ liệu mà thôi.[FLASH]http://www.swfcabin.com/open/1320370008[/FLASH]Hiện nay hầu hết các mạng LAN là dùng SWITCH, trừ các thiết bị có số port ít (<=5 port) là còn dùng HUB.Việc gửi broadcast trên mạng LAN chỉ dùng cho một số trường hợp đặc biệt, ví dụ như cho giao thức ARP, hay khi giao thức IP yêu cầu gửi broadcast (giao thức IP thì có 3 hình thức gửi là unicast, multicast và broadcast). Địa chỉ MAC broadcast được qui định là FF:FF:FF:FF:FF:FF.Mặc định, việc gửi broadcast ra ngoài phạm vi mạng LAN là không được phép, các router sẽ chặn các bản tin broadcast và không cho nó ra khỏi phạm vi mạng LAN.

Hy vọng câu trả lời trên giải đáp được thắc mắc của em, nếu còn chưa rõ cứ hỏi tiếp nhé.

Câu hỏi:ARP: Em nghĩ tới 1 cái là: mình ko đánh 192.xxx.x.x nữa mà có thể đánh chữ nào đó như avrnet và Enter thì có hiển thị được webserver không ạ? Và vai trò của ARP trong trường hợp này thế nào.Lại một câu hỏi rất đáng để bàn luận nữa, tuy nhiên vấn đề này không thuộc giao thức ARP mà thuộc một hệ thống khác, ta gọi là hệ thống tên miền (domain name system).Đúng ra, máy tính chỉ gửi và nhận dữ liệu trên mạng dựa vào địa chỉ IP. Tức là lẽ ra để truy cập vào trang chủ yahoo chẳng hạn, ta phải gõ địa chỉ IP server của yahoo: http://98.137.149.56 (các bạn có thể thử).Tuy nhiên rõ ràng là cách này quá khó nhớ với người sử dụng, do đó người ta mới nghĩ ra một loại địa chỉ gợi nhớ dễ nhớ hơn, đó là tên miền (domain name) như vậy thay vì gõ: http://98.137.149.56 ta có thể gõ: http://www.yahoo.com.Nhưng vấn đề lại phát sinh là máy tính thì không hiểu tên miền, đối với nó thì nhất thiết phải có địa chỉ IP thì mới gửi nhận dữ liệu trên mạng được.Vì vậy người ta duy trì trên mạng Internet một hệ thống server gọi là Domain Name System Server, viết tắt là DNS server (nếu chúng ta vào phần cấu hình địa chỉ IP tĩnh cho máy tính sẽ thấy phần này). Nhiệm vụ của các server này là khi có 1 máy tính bất kỳ hỏi nó về một tên miền, nó sẽ trả lời ngay tên miền đó ứng với địa chỉ IP nào.

Vậy bây giờ, nếu chúng ta mở trình duyệt và gõ vào đó http://www.yahoo.com thì cơ chế sẽ như sau:- Máy tính sẽ gửi đi một câu hỏi đến DNS server là: tên miền http://www.yahoo.com thì tương ứng với địa chỉ IP nào?- DNS server sẽ trả lời: Tên miền www.yahoo.com tương ứng với địa chỉ IP 98.137.149.56.- Máy tính sẽ truy cập theo địa chỉ 98.137.149.56 và tải nội dung trang web yahoo về.

Vậy để có thể sử dụng tên miền, máy tính bắt buộc phải biết ít nhất một địa chỉ DNS server. Thông tin về DNS server sẽ được cung cấp thông qua giao thức DHCP hoặc cấu hình tĩnh.Có rất nhiều DNS server trên mạng, mỗi nhà cung cấp dịch vụ Internet của Việt Nam chẳng hạn đều có 1 vài DNS server. Ví dụ VNPT có các DNS server: 203.162.4.190; 203.162.4.191; 203.162.4.1. Viettel có 203.113.131.1; 203.113.131.2; FPT có 210.245.0.11,...Vậy làm cách nào để có được tên miền của riêng mình (ví dụ www.avrnet.vn)? Câu trả lời là phải đăng ký tên miền (trả phí) và trả chi phí hàng năm để duy trì tên miền. Lúc đó ta có quyền chỉ định tên miền đó tương ứng với địa chỉ IP nào.Nếu không có điều kiện mua tên miền, ta có thể sử dụng 1 số tên miền miễn phí (ví dụ của dyndns). Đó là cách mà ta sẽ dùng cho project này (đến phần cuối cùng nhé).

Thầy có thể so sánh Add luận lí và Add MAC được không ạ?Tại sao trương MAC đích khi truyền ra internet lại là địa chỉ Gateway? gateway giống MAC ở điểm nào, mà lại đc điền vào MAC đích?Và ARP cache có giống với bảng NAT ip không ạ? Em thấy na lá. Có phải Gateway and local IP(mạng LAN) của host sẽ cho địa chỉ MAC của host đích không ạ?Mong thầy trả lời!Để hiểu về 2 loại địa chỉ ta có thể hình dung như thế này:Giả sử ta cần gửi 1 bức thư loanh quanh trong xóm, ta chỉ cần ghi tên người nhận là được, trong xóm sẽ dễ dàng xác định ông Nguyễn Văn A ở chỗ nào.Tuy nhiên khi gửi thư đi trong phạm vi cả nước, nếu ta chỉ ghi tên người nhận, thậm chí có thêm cả ngày tháng năm sinh và số chứng minh thư để bảo đảm không sợ bị trùng, thì bưu điện cũng bó tay vì không biết ông Nguyễn Văn A ở đâu, vì với địa chỉ này (gồm tên, ngày tháng năm sinh, số chứng minh thư) thì dù không sợ trùng địa chỉ, thì nó cũng không thể giúp ta xác định được ông Nguyễn Văn A đang ở tỉnh, thành phố, đường nào.Vậy ta cần một cách ghi địa chỉ khác, mà nhìn vào ta xác định được nơi cần chuyển thư tới, ví dụ tỉnh... huyện.... đường.... số nhà.Điều này hoàn toàn tương tự với địa chỉ IP và địa chỉ MAC: địa chỉ MAC mặc dù là bảo đảm không trùng nhau, nhưng nhìn vào nó ta không thể biết host có địa chỉ MAC đó đang ở đâu? Mỹ, Việt Nam hay Trung Quốc,...? Do đó địa chỉ MAC chỉ sử dụng được trong mạng LAN, nơi số lượng máy tính là ít, các thiết bị như SWITCH có thể biết hết các địa chỉ MAC trong mạng.Để gửi dữ liệu ra ngoài mạng LAN, ta cần 1 loại địa chỉ khác, một địa chỉ mà khi nhìn vào ta biết ngay là host tương ứng đang ở Mỹ hay Việt Nam, thậm chí nếu ở Việt Nam thì thuộc mạng của

nhà cung cấp dịch vụ nào. Địa chỉ thõa mãn yêu cầu này chính là địa chỉ IP.Sở dĩ như vậy vì trong địa chỉ IP, người ta chia thành 2 phần, phần đầu là địa chỉ mạng, cho biết máy tính đó đang thuộc mạng nào, phần sau là địa chỉ host, giúp phân biệt các máy tính trong mạng.Ta có thể dễ dàng nhận thấy điều này: các máy tính trong cùng mạng thì phần đầu của địa chỉ IP là giống nhau.Nhìn vào 1 địa chỉ IP, ta sẽ biết ngay máy tính tương ứng đang ở nước nào.Trong phạm vi mạng LAN, việc chuyển dữ liệu đến đích về mặt vật lý hoàn toàn chỉ cần dựa vào HUB hay SWITCH, tuy nhiên khi gửi ra ngoài mạng LAN thì HUB hay SWITCH sẽ bó tay, vì sẽ không biết được địa chỉ MAC của máy tính bên ngoài. Lúc này, HUB hay SWITCH sẽ chuyển gói tin đến một thiết bị khác có khả năng đưa gói tin ra khỏi mạng LAN, đó chính là Gateway, thiết bị kết nối giữa mạng LAN với bên ngoài. Và rõ ràng để SWITCH chuyển gói tin đến Gateway, thì bây giờ địa chỉ MAC đích trong frame ethernet phải là địa chỉ MAC của Gateway.ARP cache hoàn toàn khác bảng NAT IP (NAT-Network Address Translation)Trong project này sẽ hướng dẫn đến kết nối, cài đặt modem, đăng ký tên miền, thiết kế website,... nói chung từ A-Z luôn.

Có lẽ mình cần dừng lại để giải thích rõ hơn về hoạt động của 2 giao thức IP và ARP một chút, cũng như các sử dụng 2 loại địa chỉ IP và MAC trên mạng, trước khi viết code tiếp, tuy hơi dài dòng 1 chút nhưng sẽ giúp mọi người hiểu rõ hơn cách thức làm việc của TCP/IP, như vậy thì sẽ dễ hiểu code hơn và có thể tự viết hay sửa đổi code được dễ dàng.

Ta hãy xem xét 1 mạng ví dụ như sau:- Mạng LAN tại nhà gồm 3 máy tính và 1 board mạch của chúng ta kết nối vào ADSL router, từ đó nối vào mạng của nhà cung cấp dịch vụ.- Các bạn cũng cần biết là thực ra modem ADSL hay ADSL router mà ta dùng ở nhà, thật ra bên trong nó gồm 3 thiết bị: một HUB để mở rộng số lượng port, cho phép nhiều máy tính có thể cùng kết nối vào mạng; một Router IP đóng vai trò Gateway, thực hiện chức năng định tuyến giữa mạng bên trong (LAN) và mạng bên ngoài (WAN); và cuối cùng là 1 modem (Modulation - Demodulation) để có thể truyền dữ liệu trên đường dây ADSL.

Ta xẽ xem xét 2 ví dụ:Ví dụ A: board mạch của chúng ta gửi dữ liệu đến 1 máy tính trong cùng mạng LAN, ví dụ là máy có địa chỉ 192.168.1.6.Ví dụ B: board mạch gửi dữ liệu đến 1 máy tính nằm bên ngoài, ví dụ là máy có địa chỉ 203.162.44.164A-Trường hợp gửi trong mạng LANBước 1: Giao thức IP trong board mạch nhận được yêu cầu gửi dữ liệu đến địa chỉ IP 192.168.1.6Bước 2: Nó đi hỏi giao thức ARP (thông qua hàm ArpIpOut) về địa chỉ này. ARP sau khi tìm trong bảng ARP cache không thấy, nó sẽ gửi 1 bản tin ARP request dưới hình thức broadcast đến mọi máy tính trong mạng. Máy tính có địa chỉ tương ứng sẽ trả lời.

Bước 3: ARP sẽ cập nhật bảng ARP cache và trả lời lại cho giao thức IP.Bước 4: giao thức IP dùng thông tin này để điền vào frame ethernet và chuyển sang giao thứ ethernet để gửi đi.

B-Trường hợp gửi ra ngoài mạng LANNếu vẫn làm theo cách cũ thì sẽ xảy ra trường hợp như sau:

Như vậy, nếu vẫn làm theo cách cũ, việc gửi dữ liệu sẽ thất bại.Mọi việc phải được tiến hành như sau:

Vậy bây giờ ta bắt đầu viết code cho các giao thức ip và arp:Ta tạo file “ip.c” với nội dung ban đầu”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include <avr/io.h>#include "packet.h"#include "ethernet.h"#include "arp.h"#include "ip.h"

Và file header “ip.h”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef IP_H#define IP_H

#endif //IP_H

Ta cũng tạo 2 file tương ứng cho giao thức ARP:“arp.c”

Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include <avr/io.h>#include "packet.h"#include "ethernet.h"#include "arp.h"#include "ip.h"

“arp.h”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef ARP_H#define ARP_H//----------------------------------------------------------------------------

#endif //ARP_H

Như ta thấy ở trên, để giao thức IP hoạt động, nó cần biết một số thông tin cơ bản:- Địa chỉ IP của nó.- Subnet Mask của nó (có thời gian se giải thích Subnet mask sau).- Địa chỉ IP của Gateway.- Địa chỉ MAC của nó.Ta sẽ lưu các thông tin này trong một biến kiểu struct là ipConfig. Mở file “ip.h”, khai báo kiểu struct này vào:Code://----------------------------------------------------------------------------#include "packet.h"

struct ipConfig ///< IP addressing/configuration structure{

unsigned long ip; ///< IP addressunsigned long netmask; ///< netmaskunsigned long gateway; ///< gateway IP addressstruct ntEthAddr ethaddr;

};

Mở tiếp file “ip.c”, khai báo biến IpMyConfig có kiểu là struct ipConfigCode://--------------------------------------------------------------------------------------struct ipConfig IpMyConfig; ///< Local IP address/config structure

Tiếp theo ta viết trong file “ip.c” một số hàm chức năng cho giao thức này:Đầu tiên là hàm để tính trường kiểm tra lỗi (checksum) trong Header IP. Nếu các bạn xem lại phần cấu trúc Header của IP sẽ thấy nó có 1 trường kiểm tra lỗi cho Header (không bao gồm data). Trường này giúp phía nhận gói IP kiểm tra lại xem thông tin chứa trong Header (rất quan trọng) có bị sai trong quá trình truyền hay không. Nếu có sai sót, gói tin đó sẽ bị hủy bỏ mà không xử lý.Phía phát trước khi gửi phải tính giá trị checksum và ghi nó vào trường checksum trong header. Phía thu khi nhận gói tin sẽ tự mình tính lại check sum 1 cách độc lập, sau đó so sánh với checksum mà phía phát đã tính (lưu trong header) nếu có khác biệt thì tức là có lỗi xảy ra, và gói

tin sẽ bị hủy.Vậy hàm này sẽ được giao thức IP sử dụng cả khi gửi và nhận gói tin.Code://--------------------------------------------------------------------------------------//Ham tinh checksum cho goi ipunsigned int ipChecksum(unsigned char *data, unsigned int len){ register unsigned long sum = 0;

for (;;) { if (len < 2) break;

sum += *((unsigned int *)data);data+=2;

len -= 2; } if (len) sum += *(unsigned char *) data;

while ((len = (unsigned int) (sum >> 16)) != 0) sum = (unsigned int) sum + len;

return (unsigned int) sum ^ 0xFFFF;}

Ngoài lề 1 tý: khi lập trình project này, viết đến phần tính checksum mình đã tính sai, dẫn đến gói IP không hợp lệ. Phải dùng phần mềm Wireshark bắt từng gói tin, kiểm tra lại từng bit và tính lại checksum bằng tay để sửa, mất chừng 4 tiếng cho riêng phần checksum này.

Tiếp theo là hàm để set các giá trị trong struct ipConfig:Code://--------------------------------------------------------------------------------------//Set cac gia tri cau hinh cho giao thuc ipvoid ipSetConfig(unsigned long myIp, unsigned long netmask, unsigned long gatewayIp){

/*// set local addressingIpMyConfig.ip = myIp;IpMyConfig.netmask = netmask;IpMyConfig.gateway = gatewayIp;ethGetMacAddress(IpMyConfig.ethaddr.addr);*/struct ntEthAddr ethaddr;

// set local addressingIpMyConfig.ip = myIp;IpMyConfig.netmask = netmask;IpMyConfig.gateway = gatewayIp;

// set ARP associationethGetMacAddress(ethaddr.addr);arpSetAddress(&ethaddr, myIp);

}

Hàm trả lại biến con trỏ đến struct lưu thông tin config cho IP, hàm này nhằm giúp các module khác truy xuất được tới biến ipConfig thuộc module ip.Code://--------------------------------------------------------------------------------------//Tra lai con tro den struct ipConfigstruct ipConfig* ipGetConfig(void){

return &IpMyConfig;}

Hàm in ra địa chỉ MAC (dùng trong debug hoặc khi config board mạch qua cổng nối tiếp bằng command line.Code://--------------------------------------------------------------------------------------//In ra dia chi ethernetvoid ethPrintAddr(struct ntEthAddr* ethAddr){

printf("%x:%x:%x:%x:%x:%x",(ethAddr->addr[0]),(ethAddr->addr[1]),(ethAddr->addr[2]),\

(ethAddr->addr[3]),(ethAddr->addr[4]),(ethAddr->addr[5]));}

Tương tự là hàm in ra địa chỉ IPCode://--------------------------------------------------------------------------------------//In ra dia chi IPvoid ipPrintAddr(unsigned long ipaddr){

printf("%d.%d.%d.%d",((unsigned char*)&ipaddr)[3],((unsigned char*)&ipaddr)[2],((unsigned char*)&ipaddr)[1],((unsigned char*)&ipaddr)[0]);

}

Và hàm in ra các thông số cấu hình IPCode://--------------------------------------------------------------------------------------//In ra cac gia tri cau hinh cho giao thuc IPvoid ipPrintConfig(struct ipConfig* config){

printf("IP Addr : "); ipPrintAddr(config->ip); printf("\n\r");

printf("Netmask : "); ipPrintAddr(config->netmask); printf("\n\r");printf("Gateway : "); ipPrintAddr(config->gateway); printf("\n\r");

}

Lưu ý: để truy xuất được các hàm trong module “ethernet.c”, chúng ta phải thêm phần khai báo (declare) các hàm này vào file header tương ứng “ethernet.h” nhé. Từ nay việc này là đương nhiên, mình sẽ không nhắc lại nữa.Ta có sử dụng hàm printf để xuất dữ liệu ra cổng COM, các bạn tự viết nhé.Một số hàm thuộc module giao thức ARP, ta vẫn chưa viết, được gọi ở đây, nên sẽ tạo thông báo

lỗi thiếu hàm khi biên dịch.

Trên đây là một số hàm công cụ cung cấp các chức năng hỗ trợ cho giao thức IP. Tiếp theo sẽ là những hàm xử lý chính trong giao thức IP, bao gồm hàm gửi và nhận gói tin IP.

Ta viết tiếp hàm thực hiện gửi 1 gói tin qua giao thức IP:Code://--------------------------------------------------------------------------------------//Ham gui 1 goi IPvoid ipSend(unsigned long dstIp, unsigned char protocol, unsigned int len, unsigned char* ipData){

struct ntEthHeader* ethHeader;struct ntIPHeader* ipHeader;ipHeader = (struct ntIPHeader*)(ipData - IP_HEADER_LEN);ethHeader = (struct ntEthHeader*)(ipData - IP_HEADER_LEN -

ETH_HEADER_LEN);len += IP_HEADER_LEN;

ipHeader->desIPAddr = HTONL(dstIp);ipHeader->srcIPAddr = HTONL(IpMyConfig.ip);ipHeader->Protocol = protocol;ipHeader->Len = HTONS(len);ipHeader->verHdrLen = 0x45;ipHeader->ToS = 0;ipHeader->IDNumber = 0;ipHeader->Offset = 0;ipHeader->TTL = IP_TIME_TO_LIVE;ipHeader->Checksum = 0;

ipHeader->Checksum = ipChecksum((unsigned char*)ipHeader, IP_HEADER_LEN);

if( (dstIp & IpMyConfig.netmask) == (IpMyConfig.ip & IpMyConfig.netmask) )

{arpIpOut((unsigned char*)ethHeader,0);

// local send}else{

arpIpOut((unsigned char*)ethHeader,IpMyConfig.gateway);// gateway send}len += ETH_HEADER_LEN;#ifdef IP_DEBUGprintf("Sending IP packet\r\nAddr: ");ipPrintAddr(dstIp);printf("\n\rMAC: ");ethPrintAddr(&(ethHeader->desAddr));#endifethSendFrame(len, (unsigned char*)ethHeader);

}

Giải thích:- Khi hàm này được gọi, có nghĩa là giao thức lớp trên (TCP hay UDP) đã chuẩn bị sẵn dữ liệu

cần gửi đi (phần data của gói IP) và đặt lên buffer (ethernet buffer). Vậy trong hàm này ta cần điền đầy đủ thông tin để tạo nên Header IP và gọi hàm của giao thức ethernet (ethSendFrame) để yêu cầu giao thức ethernet gửi gói tin này đi.- Như vậy ở phần đầu, ta trỏ 2 biến con trỏ có kiểu là IP Header và Ethernet Header đến các vị trí tương ứng trên buffer. Sau đó lần lượt tính toán và điền các giá trị của các trường trong Header IP vào.- Tiếp theo ta gọi giao thức ARP để phân giải địa chỉ. Lúc này sẽ có 2 trường hợp: nếu địa chỉ mạng trong địa chỉ IP đích giống của chúng ta, tức là host đích nằm trong cùng mạng LAN, ta sẽ yêu cầu ARP tìm địa chỉ MAC đích để điền vào frame ethernet và gửi đi (hàm ArpIpOut). Nếu khác địa chỉ mạng, tức là host đích nằm bên ngoài mạng LAN, ta sẽ yêu cầu ARP tìm địa chỉ MAC của gateway và điền vào frame ethernet.- Sau đó ta gọi hàm của giao thức ethernet để gửi dữ liệu đi.

Trong hàm này, ta có sử dụng 1 giá trị là TTL (Time To Live) để điền vào trường TTL trong Header IP (xem lại cấu trúc Header IP). Giá trị này cần được định nghĩa trước. Ta thêm định nghĩa này vào file “ip.h”:Code:#define IP_TIME_TO_LIVE 128 //gia tri Time-To-Live (TTL) mặc định cho header IP

Có thời gian sẽ giải thích ý nghĩa của trường TTL sau nhé.

Và hàm xử lý khi nhận được 1 gói tin IP:Code://--------------------------------------------------------------------------------------//Ham xu ly goi IP, duoc goi boi giao thuc ethernet khi paket type duoc xac dinh la IPvoid IPProcess(unsigned int len, struct ntIPHeader* packet){

// check IP addressing, stop processing if not for me and not a broadcast

if( (HTONL(packet->desIPAddr) != ipGetConfig()->ip) &&(HTONL(packet->desIPAddr) != (ipGetConfig()->ip|ipGetConfig()-

>netmask)) &&(HTONL(packet->desIPAddr) != 0xFFFFFFFF) ) return;

// handle ICMP packetif( packet->Protocol == IP_PROTO_ICMP ){

#ifdef IP_DEBUGprintf("IP->Rx: ICMP/IP packet\r\n");//icmpPrintHeader((icmpip_hdr*)packet);#endificmpIpIn((struct ntIPHeader*)packet);

}else if( packet->Protocol == IP_PROTO_UDP ){

#ifdef IP_DEBUGprintf("IP->Rx: UDP/IP packet\r\n");//debugPrintHexTable(NetBufferLen-14, &NetBuffer[14]);

#endifUDPProcess(len, ((struct ntIPHeader*)packet) );

}else if( packet->Protocol == IP_PROTO_TCP ){

#ifdef IP_DEBUGprintf("IP->Rx: TCP/IP packet\r\n");#endifTCPProcess((unsigned char *)packet,len-((packet->verHdrLen &

0x0F)<<2));}else{

#ifdef IP_DEBUGprintf("IP->Rx: IP packet\r\n");#endif

}}//--------------------------------------------------------------------------------------

Giải thích:- Khi nhận được 1 gói IP, việc đầu tiên giao thức IP cần làm là kiểm tra lại địa chỉ IP xem có phải là gửi cho mình không: và ta chỉ nhận nếu đúng địa chỉ hoặc địa chỉ là IP broadcast (lưu ý đây là địa chỉ IP broadcast (255.255.255.255) nhé, không phải MAC broadcast).- Mặc dù trước đó, giao thức ethernet đã kiểm tra địa chỉ MAC rồi, nhưng giao thức IP vẫn kiểm tra lại địa chỉ IP.- Tiếp theo, ta kiểm tra trường protocol trong header IP để xem giao thức lớp trên (trên giao thức IP) nào đã gửi gói tin này (TCP, UDP, hay ICMP) và gọi hàm của giao thức tương ứng để xử lý.

Vậy là xong giao thức IP.

Tiếp theo ta viết code cho giao thức ARP.Trước hết ta cần xác định một số giá trị hằng dành cho giao thức ARP. Đó là số dòng tối đa trong bảng ARP cache, thời gian timeout cho mỗi cặp địa chỉ trong bảng ARP cache.Ta mở file “arp.h” thêm vào các định nghĩa này:Code://--------------------------------------------------------------------------------------#include "packet.h"//--------------------------------------------------------------------------------------#ifndef ARP_TABLE_SIZE#define ARP_TABLE_SIZE 8#endif

#ifndef ARP_CACHE_TIME_TO_LIVE#define ARP_CACHE_TIME_TO_LIVE 250#endif

Ta cũng khai báo trong “arp.h” một biến kiểu struct cho mỗi dòng trong bảng ARP cache, gồm 1 địa chỉ IP và địa chỉ MAC tương ứng với địa chỉ IP đó:Code:

//--------------------------------------------------------------------------------------struct ARPentry{

unsigned long ipAddr; ///< remote-note IP addressstruct ntEthAddr ethAddr; ///< remote-node ethernet

(hardware/mac) addressunsigned char time; ///< time to live (in

ARP table); this is decremented by arpTimer()};

Trong mỗi entry, còn có thêm 1 biến là timeout. Như đã nói ở phần trước, mỗi entry trong ARP cache không tồn tại mãi có 1 thời gian timeout nhất định, biến timeout này sẽ được gán 1 giá trị ban đầu mỗi khi được cập nhật, và sẽ tự động giảm theo thời gian (nhờ sử dụng ngắt timer). Nếu 1 entry quá lâu mà không được cập nhật (biến timeout giảm về 0) thì nó sẽ bị xóa đi (thực ra ta không cần xóa mà chỉ cần xem các entry có biến timeout = 0 là entry trống).

Ta quay lại file “arp.c”, khai báo một ARPentry để lưu cặp địa chỉ IP – địa chỉ MAC của chính board mạch và một array của ARPentry để làm bảng ARP cache:Code://--------------------------------------------------------------------------------------struct ARPentry ARPMyAddr;struct ARPentry ARPTable[ARP_TABLE_SIZE];

Tiếp theo ta viết các hàm cho giao thức ARP:Đầu tiên là hàm khởi tạo giao thức ARP (thực chất là khởi động giá trị các biến trong ARP cache mà thôi):Code://--------------------------------------------------------------------------------------void arpInit(void){

unsigned char i;for(i=0; i<ARP_TABLE_SIZE; i++){

ARPTable[i].ipAddr = 0;ARPTable[i].time = 0;

}}

Hàm set địa chỉ, thực ra chỉ là khai báo địa chỉ MAC và IP của bản thân cho ARP:Code://--------------------------------------------------------------------------------------void arpSetAddress(struct ntEthAddr* ethAddr, unsigned long ipAddr){

ARPMyAddr.ethAddr = *ethAddr;ARPMyAddr.ipAddr = ipAddr;

}

Hàm tìm kiếm 1 địa chỉ IP trong bảng ARP cache, trả lại vị trí của entry tương ứng với địa chỉ IP đó trong bảng ARP cache:Code://--------------------------------------------------------------------------------------unsigned char arpSearchIP(unsigned long ipaddr)

{unsigned char i;for(i=0; i<ARP_TABLE_SIZE; i++){

if((ARPTable[i].ipAddr == ipaddr) && (ARPTable[i].time != 0)){

return i;}

}return -1;

}

Hàm cập nhật 1 entry trong bảng ARPCode://--------------------------------------------------------------------------------------void arpUpdateEntry(struct ntEthAddr ethAddr,unsigned long ipAddr){

unsigned char index;index = arpSearchIP(ipAddr);if(index < ARP_TABLE_SIZE){

ARPTable[index].ethAddr = ethAddr;ARPTable[index].time = ARP_CACHE_TIME_TO_LIVE;#ifdef ARP_DEBUGprintf("Update ARP TTL %d: ",index);ipPrintAddr(ipAddr);printf("-");ethPrintAddr(&ethAddr);printf("\n\r");#endifreturn;

}for(index=0; index<ARP_TABLE_SIZE; index++){

if(!ARPTable[index].time){

ARPTable[index].ethAddr = ethAddr;ARPTable[index].ipAddr = ipAddr;ARPTable[index].time = ARP_CACHE_TIME_TO_LIVE;#ifdef ARP_DEBUGprintf("Update ARP: ");ipPrintAddr(ipAddr);printf("-");ethPrintAddr(&ethAddr);printf("\n\r");#endifreturn;

}}

}

Hàm xử lý khi nhận được 1 bản tin ARP (do lớp giao thức ethernet chuyển đến):Code://--------------------------------------------------------------------------------------void arpArpProcess(unsigned int len, unsigned char* ethFrame){

struct ntEthHeader* ethHeader;struct ntARPHeader* arpHeader;ethHeader = (struct ntEthHeader*)ethFrame;arpHeader = (struct ntARPHeader*)(ethFrame + ETH_HEADER_LEN);#ifdef ARP_DEBUGprintf("Received ARP Request\r\n");

arpPrintHeader(arpHeader);#endifif( (arpHeader->hwType == 0x0100) &&

(arpHeader->protocol == 0x0008) &&(arpHeader->hwLen == 0x06) && (arpHeader->protoLen == 0x04) &&(arpHeader->dipaddr == HTONL(ARPMyAddr.ipAddr))){if(arpHeader->opcode == HTONS(ARP_OPCODE_REQUEST)){

arpUpdateEntry(arpHeader->shwaddr,HTONL(arpHeader->sipaddr));

arpHeader->dhwaddr = arpHeader->shwaddr;arpHeader->dipaddr = arpHeader->sipaddr;arpHeader->shwaddr = ARPMyAddr.ethAddr;arpHeader->sipaddr = HTONL(ARPMyAddr.ipAddr);arpHeader->opcode = HTONS(ARP_OPCODE_REPLY);ethHeader->desAddr = ethHeader->srcAddr;ethHeader->srcAddr = ARPMyAddr.ethAddr;#ifdef ARP_DEBUGprintf("Sending ARP Reply\r\n");arpPrintHeader(arpHeader);#endifethSendFrame(len, (unsigned char*)ethHeader);return;

}if(arpHeader->opcode == HTONS(ARP_OPCODE_REPLY)){

arpUpdateEntry(arpHeader->shwaddr,HTONL(arpHeader->sipaddr));

#ifdef ARP_DEBUGprintf("is ARP reply\r\n");#endifreturn;

}}#ifdef ARP_DEBUGprintf("Unknown ARP packet\r\n");#endif

}

hàm xử lý khi nhận được 1 gói IP: như đã nói trước đây trong nội dung về ARP, có 2 cách cập nhật ARP đó là thông qua ARP request và thông qua các gói IP đến. Mỗi khi có 1 gói IP đến, lẽ dĩ nhiên là gói này sẽ được giao thức IP xử lý, ARP không can thiệp vào quá trình xử lý đó. Tuy nhiên trong mỗi gói IP đến có 2 thông tin quan trọng mà ARP cần: đó là địa chỉ IP và địa chỉ MAC của host đã gửi gói tin đó. ARP sẽ dùng cặp địa chỉ này để cập nhật ARP cache.Code://--------------------------------------------------------------------------------------void arpIPPacketIn(unsigned char* ethFrame){

struct ntEthHeader* ethHeader;struct ntIPHeader* ipHeader;ethHeader = (struct ntEthHeader*)ethFrame;ipHeader = (struct ntIPHeader*)(ethFrame + ETH_HEADER_LEN);arpUpdateEntry(ethHeader->srcAddr,HTONL(ipHeader->srcIPAddr));

}//--------------------------------------------------------------------------------------

Cuối cùng là hàm phân giải địa chỉ để cung cấp địa chỉ MAC cho giao thức IP khi được yêu cầuCode:void arpIpOut(unsigned char* ethFrame, unsigned long phyDstIp){

unsigned char index;struct ntEthHeader* ethHeader;struct ntIPHeader* ipHeader;ethHeader = (struct ntEthHeader*)ethFrame;ipHeader = (struct ntIPHeader*)(ethFrame + ETH_HEADER_LEN);

if(phyDstIp)index = arpSearchIP(phyDstIp);

elseindex = arpSearchIP(HTONL(ipHeader->desIPAddr));

if(index < ARP_TABLE_SIZE){

ethHeader->srcAddr = ARPMyAddr.ethAddr;ethHeader->desAddr = ARPTable[index].ethAddr;ethHeader->type = HTONS(ETH_TYPE_IP);

}else{

ethHeader->srcAddr = ARPMyAddr.ethAddr;ethHeader->desAddr.addr[0] = 0xFF;ethHeader->desAddr.addr[1] = 0xFF;ethHeader->desAddr.addr[2] = 0xFF;ethHeader->desAddr.addr[3] = 0xFF;ethHeader->desAddr.addr[4] = 0xFF;ethHeader->desAddr.addr[5] = 0xFF;ethHeader->type = HTONS(ETH_TYPE_IP);

}#ifdef ARP_DEBUGprintf("ARP Result:");ipPrintAddr(ARPTable[index].ipAddr);printf("-");ethPrintAddr(&(ethHeader->desAddr));printf("\r\n");#endif

}

Trong hàm ArpIpOut trên, có 1 chỗ "lách luật", không tuân thủ đúng nguyên tắc làm việc của ARP, đố các bạn tìm ra, nếu có ai tìm ra mình sẽ giải thích tại sao lại làm như vậy.Hàm kiểm tra timeout của các entry trong ARP cache. hàm này sẽ được gọi định kỳ bởi ngắt timer để kiểm tra xem có entry nào "quá date" hay không, và hủy các entry đó.Code://--------------------------------------------------------------------------------------void arpTimer(void)//Goi moi 10s{

int index;for(index=0; index<ARP_TABLE_SIZE; index++){

if(ARPTable[index].time)ARPTable[index].time--;

}}

//--------------------------------------------------------------------------------------

Các hàm phục vụ cho mục đích debug:Code:#ifdef ARP_DEBUGvoid arpPrintHeader(struct ntARPHeader* packet){

printf("ARP Packet:\r\n");printf("Operation : ");if(packet->opcode == HTONS(ARP_OPCODE_REQUEST))

printf("REQUEST");else if(packet->opcode == HTONS(ARP_OPCODE_REPLY))

printf("REPLY");else

printf("UNKNOWN");printf("\n\r");printf("SrcHwAddr : "); ethPrintAddr(&packet-

>shwaddr);printf("\n\r");printf("SrcProtoAddr: "); ipPrintAddr(HTONL(packet-

>sipaddr));printf("\n\r");printf("DstHwAddr : "); ethPrintAddr(&packet-

>dhwaddr);printf("\n\r");printf("DstProtoAddr: "); ipPrintAddr(HTONL(packet-

>dipaddr));printf("\n\r");}//--------------------------------------------------------------------------------------void arpPrintTable(void){

unsigned char i;

// print ARP tableprintf("Time Eth Address IP Address\r\n");printf("---------------------------------------\r\n");for(i=0; i<ARP_TABLE_SIZE; i++){

printf("%d",(ARPTable[i].time));printf(" ");ethPrintAddr(&ARPTable[i].ethAddr);printf(" ");ipPrintAddr(ARPTable[i].ipAddr);printf("\n\r");

}}#endif//--------------------------------------------------------------------------------------

Sau khi viết hàm xong, nhớ thêm declare vào file header tương ứng nhé.

Đến đây là xong giao thức IP và ARP, trong phần này có gọi các hàm của giao thức TCP, UDP và ICMP mà ta vẫn chưa viết tới, nên dịch cũng sẽ bị báo lỗi thiếu 3 hàm này nhé.

Bài 5: Các giao thức lớp vận chuyểnVậy là xong các giao thức ở lớp 3, IP và giao thức hỗ trợ cho nó là ARP. Bây giờ chúng ta sẽ chuyển sang các giao thức ở lớp 4, lớp vận chuyển (Transport Layer). Đó là TCP (Transport Control Protocol) và UDP (User Datagram Protocol), ngoài ra còn có một giao thức điều khiển cũng có thể xếp vào đây, đó là ICMP (Internet Control Message Protocol).

Trước hết ta nói qua về chức năng và hoạt động cơ bản của các giao thức này trước khi bắt tay vào viết code.Nếu nói phần lập trình giao thức IP phức tạp gấp đôi so với ethernet thì có lẽ lập trình giao thức TCP phải phức tạp gấp đôi cả IP và ARP cộng lại. Vì trong giao thức TCP, ta phải xử lý các vấn đề liên quan đến quá trình bắt tay giữa hai phía khi truyền dữ liệu. Phải hỗ trợ cùng lúc nhiều kết nối TCP đến các host khác (ví dụ webserver phải có khả năng xử lý khi có đồng thời vài ba máy tính cùng truy cập vào website), và liên quan chặt chẽ đến lớp ứng dụng và dịch vụ. Chính vì vậy ta phải tìm hiểu kỹ hoạt động của nó.

So với TCP thì UDP đơn giản hơn nhiều.

Còn ICMP là một giao thức dùng để chuyển qua lại các bản tin điều khiển trên mạng. Ví dụ khi ta gõ lệnh ping trên máy tính, sẽ có 1 bản tin điều khiển là ICMP request được gửi đến địa chỉ đích. Và nơi nhận được ICMP request sẽ trả lời lại bằng bản tin ICMP reply. Nếu viết xong giao thức này thì chúng ta có thể ping đến board mạch của chúng ta được rồi.

Có lẽ ta bắt đầu với giao thức ICMP.

Giao thức thông điệp điều khiển Internet (Internet Control Message Protocol) được các router và host trên mạng Internet sử dụng để thông báo về các vấn đề gặp phải trong quá trình định tuyến dữ liệu.Việc này được thực hiện bằng cách gửi qua lại các bản tin thông báo về các sự kiện xảy ra trên mạng. Các bản tin này được gọi là thông điệp điều khiển Internet (Internet Control Message). Có rất nhiều bản tin như vậy, được dùng trong những trường hợp cụ thể khác nhau. Dưới đây là 1 số bản tin ICMP thường gặp:

- Bản tin “Echo Request” và “Echo Reply”: sử dụng cho quá trình kiểm tra (lệnh ping).- Bản tin “Source Quench”: yêu cầu host nguồn giảm tốc độ truyền dữ liệu.- Bản tin “Destination Unreachable”: thông báo cho host nguồn biết datagram không thể chuyển được đến đích.- Bản tin “Time Exceeded”: thông báo 1 gói tin bị hủy do TTL = 0.- Bản tin “Fragmentation Needed”: thông báo cho host nguồn biết 1 datagram có cờ DF (Don’t Fragment) = 1, nhưng router cần phân đoạn datagram này để chuyển đến chặng kế tiếp.

Trong phạm vi project này, chúng ta chỉ lập trình cho 2 bản tin là “Echo Request” và “Echo Reply” để phục vụ cho việc ping đến board mạch của chúng ta.

Đây là cấu trúc của bản tin ICMP:

Bản tin ICMP gồm 8 byte:- Byte đầu là trường Type: cho biết đây là bản tin gì.- Byte thứ 2 là Code: cho biết mã cụ thể cho từng trường hợp.- 2 byte kế là checksum: kiểm tra lỗi.- 4 byte còn lại chứa thông tin riêng của từng loại bản tin. Trong trường hợp bản tin “Echo Request” và “Echo Reply” thì đây là số nhận dạng (ID) và số tuần tự của bản tin.Vậy ta cần khai báo cấu trúc của bản tin ICMP vào trong file “packet.h”:Code://--------------------------------------------------------------------------------------//Cau truc ICMP headerstruct ntICMPHeader{

unsigned char Type;unsigned char Code;unsigned int Checksum;unsigned int ID;unsigned int seqNumber;

};#define ICMP_HEADER_LEN 8#define ICMP_TYPE_ECHOREPLY 0#define ICMP_TYPE_ECHOREQUEST 8

Tiếp theo ta tạo file source và file header cho module giao thức ICMP.Nội dung ban đầu của file “icmp.c”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include "packet.h"#include "ethernet.h"#include "arp.h"#include "ip.h"#include "icmp.h"

Và file “icmp.h”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------//======================================================================================// icmp.h//======================================================================================

// This is header file for icmp.c// Writen by NTTam// PTITHCM// Ver 1.0//======================================================================================#ifndef ICMP_H#define ICMP_H//--------------------------------------------------------------------------------------#include "packet.h"//--------------------------------------------------------------------------------------

#endif //ICMP_H

Bây giờ ta bắt tay vào viết code:Đầu tiên là hàm xử lý khi nhận được bản tin “Echo Request” (tức là có máy tính “ping” đến board mạch.Code://--------------------------------------------------------------------------------------//Ham xu ly goi ICMP nhan duocvoid icmpIpIn(struct ntIPHeader* ipHeader){

struct ntICMPHeader* icmpHeader;icmpHeader = (struct ntICMPHeader*)((unsigned char*)ipHeader +

IP_HEADER_LEN);// check ICMP typeswitch(icmpHeader->Type){case ICMP_TYPE_ECHOREQUEST:

// echo requesticmpEchoReply(ipHeader);break;

default:break;

}}

Chức năng hàm này khá đơn giản: ta kiểm tra trường “Type” để xem đây có đúng là bản tin “Echo Request” hay không. Nếu đúng ta gọi hàm icmpEchoReply (sẽ viết kế tiếp) để trả lời.

Tiếp theo là hàm icmpEchoReply để gửi bản tin “Echo Reply” để trả lời cho bản tin “Echo Request”Code://--------------------------------------------------------------------------------------//Ham gui di ban tin tra loi cho Echo Request (Echo Reply)void icmpEchoReply(struct ntIPHeader* ipHeader){

unsigned long tempIp;unsigned char* ethFrame;struct ntICMPHeader* icmpHeader;

icmpHeader = (struct ntICMPHeader*)((unsigned char*)ipHeader + IP_HEADER_LEN);

icmpHeader->Type = ICMP_TYPE_ECHOREPLY;icmpHeader->Checksum = 0;icmpHeader->Checksum = ipChecksum((unsigned char*)icmpHeader,

HTONS(ipHeader->Len)-IP_HEADER_LEN);tempIp = ipHeader->desIPAddr;ipHeader->desIPAddr = ipHeader->srcIPAddr;ipHeader->srcIPAddr = tempIp;ethFrame = ((unsigned char*)ipHeader);ethFrame -= ETH_HEADER_LEN;arpIpOut(ethFrame, 0);#ifdef ICMP_DEBUGicmpPrintHeader(ipHeader);#endifethSendFrame(HTONS(ipHeader->Len)+ETH_HEADER_LEN, ethFrame);

}

Nội dung thực hiện trong hàm này khá đơn giản:- Ta điền các nội dung cần thiết cho bản tin Echo Reply.- Gọi giao thức ARP để phân giải địa chỉ- Gọi hàm ethSendFrame của giao thức ethernet để gửi frame ethernet đi.

Cuối cùng là 1 hàm dùng cho mục đích debug:Code://--------------------------------------------------------------------------------------#ifdef ICMP_DEBUG//In ra Header cua goi ICPMvoid icmpPrintHeader(struct ntIPHeader* ipHeader){

struct ntICMPHeader* icmpHeader;icmpHeader = (struct ntICMPHeader*)((unsigned char*)ipHeader +

IP_HEADER_LEN);printf("ICMP Packet:\r\n");// print source IP addressprintf("SrcIpAddr: "); ipPrintAddr(HTONL(ipHeader->srcIPAddr));printf("\n\r");// print dest IP addressprintf("DstIpAddr: "); ipPrintAddr(HTONL(ipHeader->desIPAddr));printf("\n\r");// print typeprintf("Type : ");switch(icmpHeader->Type){case ICMP_TYPE_ECHOREQUEST: printf("ECHO REQUEST"); break;case ICMP_TYPE_ECHOREPLY: printf("ECHO REPLY"); break;default:printf("UNKNOWN"); break;}printf("\n\r");// print codeprintf("Code: 0x%x \n\r",(unsigned int)(icmpHeader->Code));

}#endif

//--------------------------------------------------------------------------------------

Ta cũng phải nhớ thêm declare các hàm vừa viết vào file header (icmp.h):Code:void icmpIpIn(struct ntIPHeader* ipHeader);void icmpEchoReply(struct ntIPHeader* ipHeader);void icmpPrintHeader(struct ntIPHeader* ipHeader);

Vậy là xong giao thức ICMP.Đến đây vẫn chưa ping được nhé, vì ta vẫn chưa viết ngắt timer để phục vụ cho các hàm kiểm tra thời gian timeout và nội dung cho chương trình chính (main()).

Giao thức TCP (Transport Control Protocol):

Tiếp theo là giao thức khó chịu nhất trong chồng giao thức TCP/IP. Đó chính là giao thức TCP (Transport Control Protocol)

Nhiệm vụ chính của TCP là đảm bảo dữ liệu đến đích đúng và đủ trên một môi trường truyền tải không đáng tin cậy (IP).

Để hiểu chức năng và cách mà TCP thực hiện chức năng đó, ta xem minh họa sau đây:

Đến đây có 3 trường hợp xảy ra:- TH 1: mọi việc suôn sẻ, thư ký bên B nhận được thư, gửi thư trả lời báo đã nhận được thông điệp. Thư ký bên A sẽ hủy bản photo còn lưu dữ, việc gửi thông điệp thành công.- TH2: bưu điện làm mất thư, bên B không nhận được. Như vậy thư ký bên A chờ 1 thời gian không thấy phản hồi sẽ lấy bản photo ra gửi lại lần nữa.- TH 3: thư ký bên B đã nhận được và gửi thư báo nhận, nhưng bưu điện lại làm thất lạc thư này. Thư ký bên A được một thời gian không thấy cũng sẽ gửi lại. Khi bên B nhận được thông điệp một lần nữa cũng sẽ gửi báo nhận một lần nữa.

Cấu trúc Header TCP:

Chú thích:- Số port đích và số port nguồn: để phân biệt các tiến trình ứng dụng đang xảy ra trong máy tính- Các số sequence và Acknowledgement: số sequence để phân biệt các segment khác nhau trong một dòng dữ liệu, các số Acknowledgement dùng trong cơ chế xác nhận- Vùng Data offset: chiều dài của Header tính theo đơn vị 32 bit- Một số cờ (flags):. URG (Urgent): thiết lập 1 khi có dữ liệu quan trọng cần truyền ngay.. ACK: cho biết có số xác nhận nằm trong vùng Acknowledgement. PSH (Push): được thiết lập trong trường hợp dữ liệu nên được giao tức thời. RST (Reset): chỉ thị một lỗi sai và hủy bỏ phiên làm việc. SYN (Synchronize): trong các bản tin khởi tạo khi thiết lập một kết nối truyền dữ liệu. FIN (Finish): dùng đóng 1 phiên làm việc- Vùng Window: chỉ ra số lượng không gian bộ đệm khả dụng để nhận dữ liệu- Vùng Checksum: vùng kiểm tra sai cho cả segment

- Vùng Urgent Pointer: chỉ ra chiều dài của dữ liệu urgent- Vùng Options: xác định kích thước cực đại của 1 segment

Cụ thể hơn, vai trò của TCP trong chồng giao thức TCP gồm 3 chức năng chính: điều khiển luồng, kiểm soát lỗi và báo nhận.- Điều khiển luồng: điều phối tốc độ và kích thước luồng dữ liệu để đảm bảo phía nhận đủ khả năng nhận và xử lý luồng dữ liệu.- Kiểm soát lỗi: đảm bảo các gói tin đến đúng và đủ- Báo nhận: khi nhận được dữ liệu và không có lỗi, phía nhận phải báo lại với phía gửi biết.

Để thực hiện được các chức năng đó, một quá trình truyền dữ liệu qua giao thức TCP (mà ta gọi là phiên truyền thông – session) gồm có 3 giai đoạn:- Thiết lập kết nối- Truyền dữ liệu- Giải tỏa kết nối

Để có thể hình dung được quá trình đó, mời các bạn xem minh họa sau:Thiết lập kết nối:

Truyền dữ liệu:

Để có thể giám sát chặt chẽ trạng thái và mọi sự kiện xảy ra trong 1 kết nối TCP, trạng thái của một kết nối TCP được chuyển đổi tuân theo một lưu đồ trạng thái như sau:

Đây chính là thuật toán chính mà ta phải lập trình cho giao thức TCP. Nhìn thấy sợ chưa .

Giải thích:- TCP là giao thức hướng kết nối, dạng client – server. Tức là trong 1 phiên truyền thông thì sẽ có một phía đóng vai trò client (chính là phía khởi tạo kết nối, trong ví dụ minh họa trên là công ty A, bên trái), phía còn lại, lúc nào cũng ở trạng thái chờ đợi các client thiết lập kết nối tới chính là server.- Ví dụ khi ta truy cập web, thì máy tính của ta là client, máy chủ chứa trang web chính là server, lúc nào cũng ở trạng thái đợi các máy tính client kết nối đến (và phải có khả năng thiết lập đồng thời nhiều kết nối, vì có thể có nhiều client kết nối tới cùng lúc).- Đối với mạch mà chúng ta định làm, dĩ nhiên là thông thường nó đóng vai trò server rồi.- Trong lưu đồ trên, áp dụng cho cả client và server. Cả client và server đều bắt đầu bằng trạng thái “Close”. Client sẽ thiết lập kết nối theo con đường Active Open (nó chủ động thiết lập kết nối). Server sẽ thiết lập kết nối theo con đường Passive Open (thụ động, vì nó đợi client bắt đầu mà)

Quá trình chuyển trạng thái: ta hãy xem xét kịch bản thông thường nhất.- Cả hai bắt đầu bằng trạng thái close, không có kết nối nào tồn tại.- Khi Server mở một port TCP để đợi client thiết lập kết nối, nó chuyển sang trạng thái “Listen”.- Khi client gửi đi bản tin SYN (bước số 1 trong ví dụ minh họa, giai đoạn thiết lập kết nối), nó chuyển sang trạng thái “SYN sent”.- Lúc này khi server nhận được bản tin SYN từ client và gửi đáp lại 1 bản tin SYN (bước 2 trong VD), nó chuyển sang trạng thái “SYN Received”.- Lúc này client gửi lại bản tin xác nhận ACK (bước 3 trong ví dụ), nó chuyển sang trạng thái thiết lập kết nối “Established”.- Server nhận được bản tin ACK trên của client, nó cũng chuyển sang trạng thái “Established”.- Sau đó 2 bên tiến hành truyền dữ liệu, trạng thái cả 2 phía đều là “Established”.- Một trong hai phía truyền xong dữ liệu, đến đây thì vai trò hai bên là như nhau, ta giả sử client truyền xong dữ liệu trước, nó sẽ gửi bản tin FIN, và chuyển sang trạng thái “FIN wait 1”.- Phía server nhận được bản tin này, gửi xác nhận ACK, và chuyển sang trạng thái “Close wait”.- Khi client nhận được xác nhận từ server (nhận được bản tin ACK trên) thì nó chuyển sang trạng thái “FIN wait 2”.- Đến lúc này server vẫn có thể tiếp tục gửi dữ liệu và client vẫn tiếp tục nhận (vì chỉ có client báo là gửi xong dữ liệu).- Đến khi nào server cũng gửi hết dữ liệu, nó sẽ gửi đi bản tin FIN, cho biết nó cũng đã gửi xong dữ liệu và chuyển sang trạng thái “LAST ACK”.- Khi client nhận được bản tin FIN trên từ server, nó gửi xác nhận (ACK) và chuyển sang trạng thái “Time wait”, sau đó chờ 1 khoảng thời gian Timeout và đóng kết nối, quay lại trạng thái “Close”.- Khi server nhận được nó cũng chuyển từ “Last ACK” sang “Close” (không cần đợi Timeout)

Trên đây chỉ là kịch bản thông thường nhất. Lưu đồ trên còn giải quyết cho các kịch bản khác.

Để thay đổi không khí tí. Hôm trước mình đã nói là nếu viết xong giao thức ICMP, chưa cần TCP và UDP, thì đã có thể ping được tới board mạch của chúng ta. Từ đầu đến giờ chúng ta viết code quá trời luôn mà chưa thể nạp vào chip để chạy thử 1 cái, kể cũng hơi nản. Vậy tới đây mình dừng lại để hướng dẫn bổ sung một số hàm và viết hàm main() trong file “ntAVRnet.c” để có thể biên dịch nạp vào chip Atmega32, sau đó ta sẽ thử ping tới mạch xem mạch của ta đã hoạt động chưa nhé.

Đầu tiên, ta phải viết một số hàm mà từ đầu đến giờ ta vẫn chưa viết: đó là hàm printf sử dụng uart để xuất thông tin lên cổng COM trên máy tính và ngắt cho timer.

Phần này sẽ không giải thích chi tiết vì không phải là trọng tâm chính, các bạn tự tìm hiểu nhé:Thêm vào project cặp file “uart.c” và “uart.h”Nội dung file “uart.c”:Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include <avr/io.h>#include <avr/pgmspace.h>#include <avr/interrupt.h>#include <stdarg.h>#include "ntAVRnet.h"#include "uart.h"//----------------------------------------------------------------------------char UartRxBuffer[UART_RX_BUFFER_SIZE];char UartTxBuffer[UART_TX_BUFFER_SIZE];volatile unsigned char UartTxBufferStart;volatile unsigned char UartTxBufferLen;volatile unsigned char UartRxBufferStart;volatile unsigned char UartRxBufferLen;static char HexTable[] PROGMEM= "0123456789ABCDEF";//----------------------------------------------------------------------------void uartInit(unsigned long baudrate){

unsigned int bauddiv = ((F_CPU+(baudrate*8L))/(baudrate*16L)-1);//UBRRL = bauddiv;#ifdef UBRRHUBRRH = ((bauddiv>>8) & 0x0F);// URSEL 7// UMSEL 6 0:Asynchronuos/1:Synchronous// UPM1 5 Parity mode: 00:disabled/01:Reserved/10:Even/11:Odd// UPM0 4// USBS 3 Stop bit: 0:1 bit/1:2 bit// UCSZ1 2 Char

size:000:5/001:6/010:7/011:8/111:9/others:reserverd// UCSZ0 1// UCPOL 0UCSRC = 0x80 | (1<<UCSZ1) | (1<<UCSZ0);#endifUCR =((1 << TXEN) | (1 << RXEN) | (1<< RXCIE) | (1<< TXCIE));// UartTxBufferStart = 0;UartTxBufferLen = 0;UartRxBufferStart = 0;UartRxBufferLen = 0;

sei();}//--------------------------------------------------------------------------------------SIGNAL(SIG_UART_TRANS){

if(UartTxBufferLen){--UartTxBufferLen;UDR = UartTxBuffer[UartTxBufferStart++];if (UartTxBufferStart == UART_TX_BUFFER_SIZE)

UartTxBufferStart = 0;}

}//--------------------------------------------------------------------------------------SIGNAL(SIG_UART_RECV){

unsigned char i;char status,data;status = USR;data = UDR;if ((status & ((1<<FE) | (1<<PE) | (1<<DOR))) == 0){

if(++UartRxBufferLen == UART_RX_BUFFER_SIZE)UartRxBufferLen = UART_RX_BUFFER_SIZE;

i = UartRxBufferStart+UartRxBufferLen;//Vi tri ky tu cuoi cung trong buffer

if(i > UART_RX_BUFFER_SIZE)i -= UART_RX_BUFFER_SIZE;

UartRxBuffer[i-1] = data;}

}//--------------------------------------------------------------------------------------char uartGetByte(void){

//char c;if(UartRxBufferLen){

UartRxBufferLen--;c = UartRxBuffer[UartRxBufferStart++];if(UartRxBufferStart == UART_RX_BUFFER_SIZE)

UartRxBufferStart = 0;return(c);

}return(-1);

}//--------------------------------------------------------------------------------------void uartSendByte(char c){

unsigned char i;if((USR & (1<<UDRE)) && (UartTxBufferLen == 0)){ //Neu uart dang

san sang va buffer trongUDR = c; //Gui luon

}else{//Neu uart dang ban

while(UartTxBufferLen == UART_TX_BUFFER_SIZE); //Cho neu buffer dang day

i = UartTxBufferStart + UartTxBufferLen;UartTxBufferLen++;if(i >= UART_TX_BUFFER_SIZE)

i -=UART_TX_BUFFER_SIZE;UartTxBuffer[i] = c; //Ghi vao cuoi buffer

}}//--------------------------------------------------------------------------------------int printfP(const prog_char *format, ...){

// simple printf routine// define a global HexChars or use line below//static char HexChars[16] = "0123456789ABCDEF";char c;unsigned int u_val, div_val, base;va_list ap;va_start(ap, format);for (;;){

while ((c = pgm_read_byte(format++) ) != '%'){ // Until '%' or '\0'

if (!c){

va_end(ap);return(0);

}uartSendByte(c);

}

switch (c = pgm_read_byte(format++) ){

case 'c': c = va_arg(ap,int);default: uartSendByte(c); continue;case 'd': base = 10; div_val = 10000; goto

CONVERSION_LOOP;// case 'x': base = 16; div_val = 0x10;

case 'x': base = 16; div_val = 0x1000;

CONVERSION_LOOP:u_val = va_arg(ap,int);if (c == 'd'){

if (((int)u_val) < 0){

u_val = - u_val;uartSendByte('-');

}while (div_val > 1 && div_val > u_val)

div_val /= 10;}do{

//c =pgm_read_byte(HexTable+(u_val/div_val));

uartSendByte(pgm_read_byte(HexTable+(u_val/div_val)));u_val %= div_val;div_val /= base;

} while (div_val);}

}va_end(ap);

}//--------------------------------------------------------------------------------------

Nội dung file “uart.h”:Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef UART_H#define UART_H

#include <avr/pgmspace.h>//----------------------------------------------------------------------------#define UART_TX_BUFFER_SIZE 8#define UART_RX_BUFFER_SIZE 8//--------------------------------------------------------------------------------------#ifndef UART_INTERRUPT_HANDLER#define UART_INTERRUPT_HANDLER SIGNAL#endif//define for ATmega32 register#define USR UCSRA#define UCR UCSRB#define UBRR UBRRL#define EICR EICRB#define USART_RX USART_RXC_vect #define USART_TX USART_TXC_vect //--------------------------------------------------------------------------------------void uartInit(unsigned long baudrate);char uartGetByte();void uartSendByte(char c);int printfP(const prog_char *format, ...);#define printf(format, args...) printfP(PSTR(format), ## args)//--------------------------------------------------------------------------------------#endif //UART_H

Tiếp tục hêm vào project cặp file “timer.c” và “ timer.h”Nội dung file “ timer.c”:Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include <avr/io.h>#include <avr/interrupt.h>#include "ntAVRnet.h"#include "timer.h"#include "ethernet.h"

#include "arp.h"//#include "tcp.h"//----------------------------------------------------------------------------extern volatile unsigned int time_watchdog;static volatile unsigned long UptimeMs;static volatile unsigned char Counter10ms;static volatile unsigned int Counter1s;//----------------------------------------------------------------------------void timer1Init(void){

// initialize timer 1// set prescaler on timer 1TCCR1B = (TCCR1B & ~TIMER_PRESCALE_MASK) | TIMER1PRESCALE; // set

prescalerTCNT1H = 0; // reset TCNT1TCNT1L = 0;TIMSK |= (1<<TOIE1); // enable

TCNT1 overflowTCNT1 = 0xFFFF - TIMER1_INTERVAL;

}void timerInit(void){

timer1Init();sei();

}//! Interrupt handler for tcnt1 overflow interruptTIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1){

//Tai nap gia tri timer 1TCNT1 = 0xFFFF - TIMER1_INTERVAL;//Cap nhat watchdog timerif((time_watchdog++) > 120){

time_watchdog = 0; ethInit();

}Counter1s++;arpTimer();//TCPCheckTimeOut();

}

Nội dung file “ timer.h”:Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef TIMER_H#define TIMER_H//----------------------------------------------------------------------------#define TIMER_CLK_STOP 0x00 ///< Timer Stopped#define TIMER_CLK_DIV1 0x01 ///< Timer clocked at F_CPU#define TIMER_CLK_DIV8 0x02 ///< Timer clocked at F_CPU/8#define TIMER_CLK_DIV64 0x03 ///< Timer clocked at F_CPU/64#define TIMER_CLK_DIV256 0x04 ///< Timer clocked at F_CPU/256#define TIMER_CLK_DIV1024 0x05 ///< Timer clocked at F_CPU/1024#define TIMER_CLK_T_FALL 0x06 ///< Timer clocked at T falling edge

#define TIMER_CLK_T_RISE 0x07 ///< Timer clocked at T rising edge#define TIMER_PRESCALE_MASK 0x07 ///< Timer Prescaler Bit-Mask

#define TIMER1PRESCALE TIMER_CLK_DIV64 ///< timer 1 prescaler default

#ifndef TIMER_INTERRUPT_HANDLER#define TIMER_INTERRUPT_HANDLER SIGNAL#endifvoid timer1Init(void);void timerInit(void);

#endif //TIMER_H

Mục đích chính của timer ở đây là ta tạo ra ngắt (ở đây dùng timer 1, các bạn dùng timer nào cũng được), để gọi hàm kiểm tra timeout và cập nhật watchdog timer.

Thêm define vào ntAVRnet.hCode:#define TIMER_PRESCALE 1024#define TIMER1_INTERVAL (F_CPU/TIMER_PRESCALE)

#define IPDOT(a,b,c,d) ((unsigned long)((unsigned char)a)<<24)+((unsigned long)((unsigned char)b)<<16)+((unsigned long)((unsigned char)c)<<8)+(unsigned char)d//((a<<24)|(b<<16)|(c<<8)|(d))#define IPADDRESS IPDOT(192,168,1,10)#define NETMASK IPDOT(255,255,255,0)#define GATEWAY IPDOT(192,168,1,1)

#define ETHADDR0 '0'#define ETHADDR1 'F'#define ETHADDR2 'F'#define ETHADDR3 'I'#define ETHADDR4 'C'#define ETHADDR5 'E'

Thêm include vào “ethernet.c” để từ đó có thể gọi các hàm của giao thức ip và arp:Code:#include "arp.h"#include "ip.h"

Thêm include vào file “ip.c”:Code:#include "icmp.h"#include "uart.h"

Và thêm thêm // vào trước lệnh gọi hàm UDPProcess, TCPProcess trong hàm IPProcess (vì ta vẫn chưa viết 2 hàm này của giao thức UDP và TCP).

Mở “ntAVRnet.c”:Thêm hàm khởi động các dịch vụ mạng:Code:void netInit(unsigned long ipaddress, unsigned long netmask, unsigned long gatewayip){

// init network device driver#ifdef NET_DEBUGprintf("Initializing Network Device\r\n");#endifethInit();// init ARP#ifdef NET_DEBUGprintf("Initializing ARP cache\r\n");#endifarpInit();// init addressing#ifdef NET _DEBUGprintf("Initializing Addressing\r\n");#endifipSetConfig(ipaddress, netmask, gatewayip);//dhcpInit();//TCPInit();//httpInit();

}

Hàm xuất ra cổng serial các thông tin cấu hình IP:Code://--------------------------------------------------------------------------------------void PrintIPConfig(){

printf("MAC Address: ");ethPrintAddr(&IpMyConfig.ethaddr); printf("\n\r");printf("IP Address: "); ipPrintAddr(IpMyConfig.ip);

printf("\n\r");printf("Subnet Mask: "); ipPrintAddr(IpMyConfig.netmask);

printf("\n\r");printf("Default Gateway: "); ipPrintAddr(IpMyConfig.gateway);printf("\n\r");

}

Để hàm này có thể truy xuất biến IpMyConfig nằm trong module “ip.c”, ta thêm dòng khai báo sau vào đầu file “ntAVRnet.c”.Code:extern struct ipConfig IpMyConfig;

Và viết hàm khởi động hệ thống:Code://--------------------------------------------------------------------------------------void SystemInit(){

timerInit();uartInit(UART_BAUDRATE);

}

Và cuối cùng là viết hàm main():Code:int main(void){

SystemInit();printf("\r\nNTTam AVR network testing with enc28j60.\r\n");printf("Initializing Network Interface and Stack\r\n");

printf("Ethernet chip init\r\n");IpMyConfig.ethaddr.addr[0] = ETHADDR0;IpMyConfig.ethaddr.addr[1] = ETHADDR1;IpMyConfig.ethaddr.addr[2] = ETHADDR2;IpMyConfig.ethaddr.addr[3] = ETHADDR3;IpMyConfig.ethaddr.addr[4] = ETHADDR4;IpMyConfig.ethaddr.addr[5] = ETHADDR5;IpMyConfig.ip = IPADDRESS;IpMyConfig.netmask = NETMASK;IpMyConfig.gateway = GATEWAY;netInit(IpMyConfig.ip, IpMyConfig.netmask, IpMyConfig.gateway);PrintIPConfig();while(1){

ethService();}return 0;}

Biên dịch, nạp chip và thử gõ lệnh « ping 192.168.1.10 » xem sao.Địa chỉ IP, Subnet và gateway các bạn có thể thay đổi ở các define trong file « ntAVRnet.h » cho phù hợp với mạng ở nhà mình nhé.

Thầy giải thích dÙm em cái hàm ipchecksum.Em tưởng check sum là cộng từng byte dữ liệu lại thôi.Sao mà lại có cái while lạ thế.Hehe, bởi vậy, lúc viết code chỗ này tôi cũng tưởng vậy, cứ nghĩ là mình biết rồi, té ra là chưa biết gì cả (hay nói chính xác hơn là biết chưa tới). Nguyên nhân là thế này:- Đúng là checksum là cộng lại nhưng:1-Cộng từng word (16 bit) chứ không phải từng byte2-Checksum IP được quy định là: "bù-1 16 bit của tổng bù-1 của tất của các từ 16 bit trong header IP".Nghe hơi khó hiểu, cụ thể là thế này:- Đầu tiên ta cộng các từ 16 bit trong header:Code: for (;;) { if (len < 2) break;

sum += *((unsigned int *)data);data+=2;

len -= 2; }

Nên nhớ data là biến con trỏ đến kiểu char, vì vậy ta ép kiểu nó sang biến con trỏ kiểu int: (unsigned int *)data và lấy giá trị tại địa chỉ con trỏ thì được số 16 bit: *((unsigned int *)data). Tiếp theo ta tăng biến con trỏ thêm 2 byte và trừ len đi 2 byte. Lặp đến khi nào số byte còn lại nhỏ hơn 2.Đến đây, nếu số byte là chẵn, len=0, nếu số byte lẻ (chia từng cặp 2 byte còn thừa lại 1 byte) thì len=1. Ta cộng nốt byte đó vào (xem nó như là 1 từ 2 byte mà byte cao bằng 0x00):Code: if (len)//Tuc la len=1, con thua 1 byte sum += *(unsigned char *) data;

Như vậy là ta tính tổng của header xong, nhưng nên nhớ khi cộng như vậy trong AVR, kết quả sẽ là tổng bù-2 chứ không phải tổng bù-1 như yêu cầu.Để chuyển tổng bù-2 sang bù-1, ta cần cộng phần dư vào, chính vì lý do này mà ta khai báo biến sum là biến long, có chiều dài 4 byte, hai byte cao của nó chứa phần dư.Code: while ((len = (unsigned int) (sum >> 16)) != 0) sum = (unsigned int) sum + len;

Ở đây ta gán len=(unsigned int) (sum >> 16) chính là 2 byte cao của biến 4 byte sum, lúc này biến len được tận dụng như là 1 biến tạm thôi, không có ý nghĩa là chiều dài nữa.Ta đem phần dư (2 byte cao, được chứa trong biến len) cộng với 2 byte thấp: (unsigned int) sum (phải ép kiểu sum từ long sang int nếu không nó sẽ cộng cả 4 byte của sum).Vòng lặp này cho đến khi nào 2 byte cao của sum=0x0000 (hết phần dư).Vậy kết quả ta thu được bây giờ là :"tổng bù-1 của tất của các từ 16 bit trong header IP".Cuối cùng ta cần lấy bù-1 của kết quả này, đó là checksum IP.Code: return (unsigned int) sum ^ 0xFFFF;

Phức tạp chưa. Đã nói là khi viết phần này tôi mất 4h chỉ để sửa hàm checksum và debug các gói IP để xem mình sai chỗ nào. Em thắc mắc cũng không có gì lạ.

Lúc trước tôi không mô phỏng, mà nói chung trước giờ hiếm khi nào mô phỏng trước khi làm mạch, vì mạch phức tạp thì việc mô phỏng không có nhiều ý nghĩa, mà mạch đơn giản thì cần gì phải mô phỏng, chỉ mô phỏng mạch analog thôi.Đúng là tôi quên mất define thông số này, các bạn thêm vào, tốc độ tùy mọi người thôi. Ví dụ:Code:#define UART_BAURATE 9600l

Thầy và mọi người trên diễn đàn, cho hỏi câu này: Em bị lỗi debug như hình. Có kiếm trên mạng cách sửa(edit include), có sửa lỗi được kha khá, but cái thằng lỗi này tưởng na lá với các lỗi khác, mà lại không sửa được. Mong mọi người chỉ giúp!File Ðính Kèm 35158Lỗi đó có nghĩa là kiểu struct APentry được định nghĩa 2 lần.Lỗi này thường phát sinh do file arp.h được include 2 lần vào 1 file code. Chính vì vậy mà trong mỗi file header, tôi đều thêm vào đầu file:Code:#ifndef ARP_H#define ARP_H

và cuối file:Code:#endif //ARP_H

Các dòng này nhằm mục đích nếu file được include nhiều hơn 1 lần, thì các lần sau nó sẽ được bỏ qua (vì điều kiện #ifndef ARP_H sẽ false, do ARP_H đã được định nghĩa trước đó.Còn 1 lý do có thể dẫn đến lỗi này là do khai báo trùng APentry ở đâu đó (ít có khả năng xảy ra).Nếu tìm không ra lỗi, em có thể gửi toàn bộ code lên DĐ hay qua mail, tôi sẽ check cho.

bác phải post cả project lên chứem debug ok rồi, mà mô phỏng = proteus,hiển thị lên terminal toàn kí tự Ai Cập thôi, he heXem lại baudrate, set fulse cho system clock, tần số thạch anh, các define tốc độ CPU.

Thấy mọi người build code gặp nhiều vấn đề, nên tôi cũng thử tạo project theo code hướng dẫn trên thread và buid thử, để xem có sai sót gì không:

Chỉ có 1 lỗi là thiếu khai báo baudrate mà bạn hieppro89 đã chỉ ra. Các bạn thêm khai báo này vào file "ntAVRnet.h" nhé, file này chúng ta sẽ lưu mọi thông tin cấu hình chung, để sau này có thể sửa đổi dễ dàng.

Baudrate tùy mọi người quyết dịch thôi, thấp thì ít sai bit hơn (hiện ra chữ Ai Cập ), nhưng thực ra nếu mạch làm chuẩn và tốc độ chip set đúng thì ít khi sai bit lắm.Nạp thử vào chip bằng mạch nạp tự chế:

Thử gắn cáp mạng vào và ping tới mạch:

Vậy là OK nhé, code không có sai sót. Tới đây thì chỉ có vậy thôi vì chúng ta mới viết tới giao thức ICMP, để trả lời cho lệnh ping.Các thứ mắm muối gia vị như LCD, ADC,... mọi người tự mình thêm vào nhé.Đang suy nghĩ xem có nên post toàn bộ project vừa build lên không, nhưng kết luận là không. Vì thread này lập ra nhằm giúp mọi người tự mình viết code, hay ít nhất cũng hiểu được code và modify cho phù hợp với mục đích của mình, tự xây dựng project của mình, không khuyến khích "download and build"

Mọi người thông cảm nhé, vì máu giáo viên mà . Với lại tôi từng gặp rất nhiều bạn download mạch và source code trên mạng về làm, không hiểu tí gì về mạch cũng như source, nhưng cứ bảo là mình làm được.Nếu ai thực sự gặp khó khăn và không thể tự build code được, cứ liên hệ trực tiếp, tôi sẵng sàng giúp.Còn 4 giao thức nữa: TCP, UDP, HTTP, và DHCP là ta có thể hoàn thành project này. Dạo này công việc ngập đầu, chưa có thời gian viết tiếp tut, mọi người thông cảm nhé. Vì phải giải thích code và giao thức nên cũng mất nhiều thời gian.

À quên khi khai báo define cho baudrate, các bạn phải thêm ký tự l (long) vào cuối nhé:Code:#define UART_BAUDRATE 9600l

Để cho trình biên dịch biết đó là giá trị long, vì trong hàm khởi tạo uart, biến tương ứng là biến long.

Bây giờ là phần code cho TCP :Ta lại tạo 2 file source và header :

“tcp.c” :Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include "packet.h"#include "ethernet.h"#include "ip.h"#include "uart.h"#include "tcp.h"

“tcp.h” :Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef TCP_H#define TCP_H

#endif //TCP_H

Đầu tiên là mở file “packet.h”, thêm mô tả TCP header vào :Code://--------------------------------------------------------------------------------------//Cau truc TCP headerstruct ntTCPHeader{

unsigned int srcPort;unsigned int desPort;unsigned long seqNumber;

unsigned long ackNumber;unsigned char Offset;unsigned char Flags;unsigned int Window;unsigned int Checksum;unsigned int UrgentPtr;unsigned char optdata[8];

};#define TCP_HEADER_LEN 20

Định nghĩa các cờ (flags) trong header TCP: vì khi truy xuất buffer, ta chỉ truy xuất được từng byte, sau đó ta dựa vào mask của các cờ được định nghĩa ở đây để truy xuất tới cờ tương ứng.Code:#define TCP_NON_FLAG (0)#define TCP_FIN_FLAG (1<<0)#define TCP_SYN_FLAG (1<<1)#define TCP_RST_FLAG (1<<2)#define TCP_PSH_FLAG (1<<3)#define TCP_ACK_FLAG (1<<4)#define TCP_URG_FLAG (1<<5)#define TCP_ECE_FLAG (1<<6)#define TCP_CWR_FLAG (1<<7)

Tiếp theo ta quay lại “tcp.h”, định nghĩa một số hằng sẽ sử dụng cho giao thức TCPĐầu tiên là giá trị MSS (Max Segment Size – Kích thước đoạn dữ liệu tối đa mà ta có thể nhận). Chỗ này các bạn xem lại minh họa chỗ 2 cô thư ký là hiểu ý nghĩa giá trị này liền.Code:#define MAX_SEGMENT_SIZE (ETHERNET_BUFFER_SIZE - ETH_HEADER_LEN - IP_HEADER_LEN - TCP_HEADER_LEN)

Tiếp theo là định nghĩa các trạng thái trong TCP (xem lại phần lưu đồ chuyển đổi trạng thái nhé).Code://List the TCP session state#define TCP_STATE_CLOSED 0#define TCP_STATE_SYN_SENT 1#define TCP_STATE_LISTEN 2#define TCP_STATE_SYN_RECEIVED 3#define TCP_STATE_ESTABLISHED 4#define TCP_STATE_FIN_WAIT1 5#define TCP_STATE_FIN_WAIT2 6#define TCP_STATE_CLOSING 7#define TCP_STATE_TIMED_WAIT 8#define TCP_STATE_CLOSE_WAIT 9#define TCP_STATE_LAST_ACK 10

Định nghĩa thời gian timeout cho 1 kết nối TCP: cái này rất quan trọng, có thể trong 1 phiên TCP đang kết nối, chưa đến giai đoạn giải tỏa, nhưng phía bên kia vì lý do nào đó mà ngừng liên lạc, nếu chờ hết Timeout ta phải giải tỏa kết nối đó để giải phóng bộ nhớ.Code://60 seconds timeout:#define TCP_TIMEOUT 60

Số kết nối đồng thời tối đa cho phép:Code://maximum connection count#define TCP_MAX_SESSION 8

Tiếp theo ta khai báo 1 kiểu struct để lưu thông tin về 1 kết nối:Code:

//--------------------------------------------------------------------------------------struct tcpSession{

unsigned int desPort; //Port on the remote hostunsigned int srcPort; //Port on the local hostunsigned long desIP; //IP address of the remote hostunsigned long seqNumber; //Sequence numberunsigned long ackNumber; //Acknowlegement numberunsigned char sesState; //Current state of TCP sessionunsigned int srcWin;unsigned int desWin;unsigned long lastRxAck; //Last Received Ackunsigned char nextAck;unsigned char timeOut; //Session time outvoid(*appDataIn)(unsigned char* dataBuffer,unsigned int dataLen,struct

tcpSession *pSession);unsigned char appID; //Upper layer application ID unsigned char appState; //Upper layer application state

};

Bây giờ chuyển sang “tcp.c”:Khai báo 1 bảng TCP session, trong đó mỗi dòng là 1 struct tcpSession lưu thông tin về 1 kết nối:Code://--------------------------------------------------------------------------------------struct tcpSession tcpSessionTable[TCP_MAX_SESSION];

Viết hàm khởi tạo số tuần tự (sequence number) cho 1 kết nối TCP. Các bạn xem lại phần minh họa (2 cô thư ký). Mỗi phía sẽ tự chọn 1 số tuần tự để bắt đầu. Trên máy tính, con số này thường được lấy theo thời gian. Ở đây cho đơn giản, ta chỉ chọn đại 1 số mà thôi.Code://--------------------------------------------------------------------------------------//Ham khoi tao so tuan tu cho mot phien TCP// Hien tai su dung gia tri 1234 (may tinh thuong dung gia tri thoi gian hien tai)unsigned long TCPInitSequenceNumber(){

return(1234);}

Tiếp theo là hàm đóng 1 phiên TCP:Code://--------------------------------------------------------------------------------------//Ham dong mot phien TCPvoid TCPCloseSession(unsigned char socketnum){

tcpSessionTable[socketnum].sesState = TCP_STATE_CLOSED;#ifdef TCP_DEBUGprintf("Close TCP session %d\r\n",socketnum);#endif

}

Viết hàm khởi tạo các biến cho giao thức TCP:Code:

//--------------------------------------------------------------------------------------//Khoi dong cac gia tri trong bang TCP sessionvoid TCPInit(){

unsigned char i = 0;for(i=0; i<TCP_MAX_SESSION; i++){

TCPCloseSession(i);}

}

Code:Hàm tìm kiếm 1 phiên TCP đang rỗi trong bảng TCP session table (để mở kết nối mới)//--------------------------------------------------------------------------------------//Tim mot session TCP dang roiunsigned char TCPGetFreeSession(){

unsigned char i;for(i=0; i<TCP_MAX_SESSION; i++){

if (tcpSessionTable[i].sesState == TCP_STATE_CLOSED)return i;

}//no free closed socket fount! -> kick an TIMED_WAIT socketfor(i=0; i<TCP_MAX_SESSION; i++){

if (tcpSessionTable[i].sesState == TCP_STATE_TIMED_WAIT){TCPCloseSession(i);return i;

}}//no more free sockets ... return invalid valreturn(TCP_MAX_SESSION);

}

Viết hàm khởi tạo một kết nối mới (ở chế độ server):Code://--------------------------------------------------------------------------------------//Ham khoi tao mot session TCP o che do server de cho ket noivoid TCPCreateSession(unsigned int sourcePort, prog_void* appService){

unsigned char i;i = TCPGetFreeSession();if(i >= TCP_MAX_SESSION)

i = 0; //force session 0tcpSessionTable[i].srcPort = sourcePort;tcpSessionTable[i].sesState = TCP_STATE_LISTEN; //Current

state of TCP sessiontcpSessionTable[i].srcWin = 8192;//NETSTACK_BUFFERSIZE -

ETH_HEADER_LEN - IP_HEADER_LEN - TCP_HEADER_LEN - 16;tcpSessionTable[i].desWin = tcpSessionTable[i].srcWin;tcpSessionTable[i].timeOut = TCP_TIMEOUT; //Session time

outtcpSessionTable[i].appDataIn = appService;#ifdef TCP_DEBUGprintf("TCP session created: %d\r\n", i);#endif

}

Hàm kiểm tra Timeout của các phiên TCP đang kết nốiCode://--------------------------------------------------------------------------------------//Duoc goi moi giay de kiem tra Time out cho cac phien TCP,// giai phong cac phine TCP bi treovoid TCPCheckTimeOut(){

unsigned char i;for(i=0; i<TCP_MAX_SESSION; i++){

//decrement ttl:if ((tcpSessionTable[i].sesState != TCP_STATE_CLOSED) &&

(tcpSessionTable[i].sesState != TCP_STATE_LISTEN)){if(tcpSessionTable[i].timeOut)

tcpSessionTable[i].timeOut--;

//if socket TTL count is zero, close this socket!if (tcpSessionTable[i].timeOut == 0){

TCPCloseSession(i);}

}}

}

Hàm tính checksum cho gói TCPCode://--------------------------------------------------------------------------------------//Tinh checksum cho goi TCPunsigned int checksum(unsigned char *buffer, unsigned int len, unsigned long csum32){

unsigned int res16 = 0x0000;unsigned char data_hi;unsigned char data_lo;while(len > 1){

data_hi = *buffer++;data_lo = *buffer++;res16 = (((unsigned int)data_hi << 8) + data_lo);csum32 = csum32 + res16;len -=2;

}if(len > 0){

data_hi = *buffer;res16 = (unsigned int)data_hi<<8;csum32 = csum32 + res16;

}while(csum32>>16)

csum32 = (csum32 & 0xFFFF)+ (csum32 >> 16);//csum32 = ((csum32 & 0x0000FFFF)+ ((csum32 & 0xFFFF0000) >> 16));res16 =~(csum32 & 0x0000FFFF);return (res16);

}

Tiếp theo là hai hàm chính của giao thức:

Hàm gửi 1 đoạn dữ liệu đi bằng giao thức TCP:

Code://--------------------------------------------------------------------------------------//Gui di mot goi TCPvoid TCPPackedSend(struct tcpSession *pSession, unsigned char Flags, unsigned int len, unsigned char *dataBuffer){

unsigned int tmp;unsigned long checksum32;//Make pointer to TCP headerstruct ntTCPHeader* tcpHeader;struct ntIPHeader* ipHeader;//Neu dang syn thi them option ve MSSif(Flags & TCP_SYN_FLAG){

//Option datadataBuffer[0] = 0x02;dataBuffer[1] = 0x04;dataBuffer[2] = (MAX_SEGMENT_SIZE >> 8) & 0xff;dataBuffer[3] = MAX_SEGMENT_SIZE & 0xff;dataBuffer[4] = 0x01;dataBuffer[5] = 0x03;dataBuffer[6] = 0x03;dataBuffer[7] = 0x00;//Move data pointer to make room for TCP header

}dataBuffer -= TCP_HEADER_LEN;tcpHeader = (struct ntTCPHeader*)dataBuffer;//Fill UDP headertcpHeader->srcPort = HTONS(pSession->srcPort);tcpHeader->desPort = HTONS(pSession->desPort);tcpHeader->seqNumber = HTONL(pSession->seqNumber);pSession->seqNumber = pSession->seqNumber + len;if(Flags & (TCP_FIN_FLAG|TCP_SYN_FLAG))

(pSession->seqNumber)++;tcpHeader->ackNumber = HTONL(pSession->ackNumber);if(Flags & TCP_SYN_FLAG){

tcpHeader->Offset = (0x07<<4);len += (TCP_HEADER_LEN + 8);

}else{tcpHeader->Offset = (0x05<<4);len += TCP_HEADER_LEN;

}tcpHeader->Flags = Flags;tcpHeader->Window = HTONS(pSession->srcWin);//((NETSTACK_BUFFERSIZE-

20-14));tcpHeader->Checksum = 0;tcpHeader->UrgentPtr = 0x0000;//Generate checksumipHeader = (struct ntIPHeader*)(dataBuffer-IP_HEADER_LEN);ipHeader->srcIPAddr = HTONL(ipGetConfig()->ip);ipHeader->desIPAddr = HTONL(pSession->desIP);ipHeader->Checksum = HTONS(len);ipHeader->TTL = 0x00;ipHeader->Protocol = IP_PROTO_TCP;checksum32 = 0;tmp = len + 12;

tmp = checksum (((unsigned char *)ipHeader+8), tmp, checksum32);tcpHeader->Checksum = HTONS(tmp);ipSend(pSession->desIP, IP_PROTO_TCP, len, (unsigned char

*)tcpHeader);}

Hàm xử lý khi nhận được 1 gói TCPCode://--------------------------------------------------------------------------------------//Ham xu ly goi TCP nhan duoc, duoc goi boi giao thuc IP (IPProcess)void TCPProcess(unsigned char *buffer, unsigned int len)//Ham xu ly cho giao thuc TCP// Duoc thuc thi khi nhan duoc mot goi TCP (goi boi netstackIPProcess)// buffer: co tro den dau goi IP (bat dau IP Header)// len : chieu dai buffer{

unsigned char i,ipHeaderLen,tcpHeaderLen;unsigned int dataLen;unsigned long tmp;struct ntIPHeader* ipHeader;struct ntTCPHeader* tcpHeader;unsigned char *tcpData;//Khoi tao cac co tro den Header IP va TCPipHeader = (struct ntIPHeader*)(buffer);ipHeaderLen = ((ipHeader->verHdrLen) & 0x0F) << 2;//tcpHeader = (struct ntTCPHeader*)(buffer+ipHeaderLen);tcpHeaderLen = ((tcpHeader->Offset) & 0xF0) >> 2;//tcpData = (buffer+ipHeaderLen+tcpHeaderLen);dataLen = HTONS(ipHeader->Len) - (ipHeaderLen + tcpHeaderLen);//Tim kiem mot phien TCP co san cho goi nayfor(i = 0; i < TCP_MAX_SESSION; i++){ //Check session table

if(tcpSessionTable[i].sesState != TCP_STATE_CLOSED){//If not closed session

if(tcpSessionTable[i].srcPort == HTONS((tcpHeader->desPort))){ //If matched local port

if(tcpSessionTable[i].desPort == HTONS((tcpHeader->srcPort))&&(tcpSessionTable[i].desIP == HTONL((ipHeader->srcIPAddr)))){

break; //Thoat khoi vong lap for, luc nay gia tri cua i chinh la chi so cua phien TCP tuong ung

}}

}}if(i == TCP_MAX_SESSION){ //Neu khong co 1 phien TCP dang ton tai

cho goi nay//Tim 1 phien dang o trang thai LISTEN (doi ket noi) cho local

port nayfor(i=0; i < TCP_MAX_SESSION; i++){

if(tcpSessionTable[i].sesState == TCP_STATE_LISTEN){if(tcpSessionTable[i].srcPort ==

HTONS((tcpHeader->desPort))){ //If matched local port//Cap nhat remote port va remote IP

tcpSessionTable[i].desPort = HTONS((tcpHeader->srcPort));

tcpSessionTable[i].desIP = HTONL((ipHeader->srcIPAddr));

//Dong thoi tao ra 1 session moi de cho ket noi khac den local port nay

TCPCreateSession(tcpSessionTable[i].srcPort,tcpSessionTable[i].appDataIn);break;

}}

}}if(i == TCP_MAX_SESSION){

#ifdef TCP_DEBUGprintf("No TCP session found\r\n");#endifreturn; //Neu khong co phien TCP nao danh cho goi nay thi thoat

ra}#ifdef TCP_DEBUGprintf("TCP session found: %d\r\n",i);#endif//Bat dau xu ly giao thuctcpSessionTable[i].timeOut = TCP_TIMEOUT; //Reset lai gia tri Time

out//Truong hop nhan duoc yeu cau reset lai ket noiif ((tcpHeader->Flags) & TCP_RST_FLAG){

//Chap nhan dong ket noiTCPCloseSession(i);return;

}//Kiem tra trang thai hien tai cua phien TCPswitch (tcpSessionTable[i].sesState){

//Neu la trang thai doi ket noi: TCP_STATE_LISTENcase(TCP_STATE_LISTEN):

//Chi xu ly neu co SYN duoc set (yeu cau thiet lap ket noi)

if ((tcpHeader->Flags) == TCP_SYN_FLAG){//Chuyen sang trang thai ke tiep la

TCP_STATE_SYN_RECEIVEDtcpSessionTable[i].sesState =

TCP_STATE_SYN_RECEIVED;//Khoi tao gia tri sequencetcpSessionTable[i].seqNumber =

HTONL(TCPInitSequenceNumber());//Ack chinh la so tuan tu nhan duoc cong 1tcpSessionTable[i].ackNumber =

HTONL((tcpHeader->seqNumber))+1;tcpSessionTable[i].desWin = HTONS((tcpHeader-

>Window));//Goi tra xac nhan va co SYN (SYN & ACK)TCPPackedSend(&tcpSessionTable[i],

(TCP_SYN_FLAG|TCP_ACK_FLAG),0,tcpData);//Tang so tuan tu len 1//tcpSessionTable[i].seqNumber++;

#ifdef TCP_DEBUGprintf("SYN received\r\n");#endif

}break;

//Neu la trang thai TCP_STATE_SYN_RECEIVEDcase(TCP_STATE_SYN_RECEIVED):

//Neu co co ACK (cho ban tin SYN & ACK truoc do)if ((tcpHeader->Flags) == TCP_ACK_FLAG){

//Kiem tra ack trong goi tin den, neu dung thi thiet lap ket noi hoan tat

if((tcpSessionTable[i].seqNumber) == HTONL((tcpHeader->ackNumber))){

tcpSessionTable[i].sesState = TCP_STATE_ESTABLISHED;

//Goi tiep theo gui di se co co ACKtcpSessionTable[i].nextAck = 1;#ifdef TCP_DEBUGprintf("Connection established\r\n");#endif

}}else{ //Neu khong dung ACK

//Khong lam gi ca, goi tin do khong hop le//TCPCloseSession(i);

}break;

//Truong hop ket noi da duoc thiet lapcase(TCP_STATE_ESTABLISHED):

//Neu nhan duoc yeu cau ket thuc ket noi tu clientif ((tcpHeader->Flags) & TCP_FIN_FLAG){

//Chuyen sang trang thai ke tiep la trang thai cho ACK cuoi

//Dung ra o day phai chuyen sang trang thai TCP_STATE_CLOSE_WAIT nhung khong can thiet

// vi o day ta co the dong ket noi ngay ma khong can cho gui xong du lieu

tcpSessionTable[i].sesState = TCP_STATE_LAST_ACK;

//Cap nhat acktcpSessionTable[i].ackNumber =

HTONL((tcpHeader->seqNumber)) + dataLen;tcpSessionTable[i].ackNumber++; //Tang 1

cho co FIN//Gui xac nhan ACK cho yeu cau dong ket noi

dong thoi thong bao san sang dong ket noi

TCPPackedSend(&tcpSessionTable[i],TCP_ACK_FLAG,0,tcpData);TCPPackedSend(&tcpSessionTable[i],

(TCP_FIN_FLAG|TCP_ACK_FLAG),0,tcpData);//Dang le truyen o trang thai CLOSE_WAIT nhung

ta thuc hien o day luonTCPCloseSession(i);

//Neu khong (dang truyen du lieu)}else{

//Kiem tra ACK tu remote hostif((tcpHeader->Flags) & TCP_ACK_FLAG){//Neu co

co ACK thi kiem tra gia tri ACK

tcpSessionTable[i].lastRxAck = HTONL((tcpHeader->ackNumber));

if ((tcpSessionTable[i].seqNumber) == HTONL((tcpHeader->ackNumber))){ //Dung ACK

#ifdef TCP_DEBUGprintf("Got ACK\r\n");#endif

}else{ //Phia ben kia khong nhan duoc du thong tin

//Sua loi o day//Process error correction here//Not finish yet, temporary just

ignore it and continue with next data//Chua thuc hientcpSessionTable[i].seqNumber =

HTONL((tcpHeader->ackNumber));#ifdef TCP_DEBUGprintf("Miss ACK:got %d\r\

nExpected:%d\n\r",HTONL((tcpHeader->ackNumber)),tcpSessionTable[i].seqNumber+1);

#endif}

}//--Ket thuc kiem tra ACK//Kiem tra sequence numbertmp = HTONL((tcpHeader->seqNumber));//Neu khong dung goi dang cho nhanif (tmp != tcpSessionTable[i].ackNumber){

//there was an error, check what to do next:

#ifdef TCP_DEBUGprintf("Incorrect seq, got:%d,expexted:

%d\r\n",tmp,tcpSessionTable[i].ackNumber);#endifif (tmp < tcpSessionTable[i].ackNumber)

{//Neu dang doi du lieu bat dau

tu byte thu n nhung ta nhan duoc doan du lieu bat dau tu (n-k)//Tinh phan du lieu thua (k = n

- (n-k))tmp =

(tcpSessionTable[i].ackNumber - tmp);//Neu doan du lieu thua it hon

du lieu nhan duocif(tmp < dataLen){

//Bo di phan du lieu thua, nhan phan con lai

tcpData += tmp;dataLen = dataLen - tmp;

}else{ //Neu tat ca du lieu nhan duoc deu thua

//Gui lai ACK, bo goi vua nhan duoc

dataLen = 0;

TCPPackedSend(&tcpSessionTable[i],(TCP_ACK_FLAG),0,tcpData);return;

}//Neu seq > ack (tuc la co 1 doan du

lieu bi mat)}else{ //tmp > tcp....

//Yeu cau gui lai

TCPPackedSend(&tcpSessionTable[i],(TCP_ACK_FLAG),0,tcpData);return;

}}//Neu thuc thi den day nghia la sequence number

== ack number (chinh xac)//--Ket thuc kiem tra so tuan tu//Kiem tra chieu dai buffer de chac chan la

chieu dai du lieu nhan duoc khong qua buffer//if (tcpData > (buffer + ETHERNET_BUFFER_SIZE))

tcpData = (buffer + ETHERNET_BUFFER_SIZE);

if ((tcpData + dataLen) > buffer + ETHERNET_BUFFER_SIZE){

dataLen = (buffer + ETHERNET_BUFFER_SIZE) - tcpData;

}////Cap nhat ack cho lan nhan ke tieptcpSessionTable[i].ackNumber =

tcpSessionTable[i].ackNumber + dataLen;#ifdef TCP_DEBUGprintf("Data length (%d), buffer size(%d)\n\

r",dataLen,(buffer + ETHERNET_BUFFER_SIZE - tcpData));printf("Ack Number (%d)\n\

r",tcpSessionTable[i].ackNumber);#endif//Goi tiep theo gui di se co co ACKtcpSessionTable[i].nextAck = 1;//Goi ham xu ly lop ung dungif(dataLen != 0){

(tcpSessionTable[i].appDataIn)(tcpData, dataLen,&tcpSessionTable[i]);

}}//--Ket thuc xu ly truong hop dang truyen du lieubreak;

//Neu la trang thai doi LAST_ACK (2 phia deu san sang dong ket noi, dang doi xac nhan ack cuoi cung)

case(TCP_STATE_LAST_ACK)://socket is closedtmp = HTONL((tcpHeader->seqNumber));//Kiem tra ACK, neu dung ACKif (tmp == tcpSessionTable[i].seqNumber + 1){

TCPCloseSession(i);}else{

//Gui lai co FIN & ACKTCPPackedSend(&tcpSessionTable[i],

(TCP_FIN_FLAG|TCP_ACK_FLAG), 0, tcpData);}

break;

//Truong hop ngat ket noi thu dong, da nhan co FIN tu remote host va xac nhan

case(TCP_STATE_CLOSE_WAIT)://Truong hop nay se khong xay ra vi o tren ta chuyen

truc tiep// sang LAST_ACK khi nhan duoc yeu cau dong ket noitcpSessionTable[i].sesState = TCP_STATE_LAST_ACK;if(dataLen){

tcpSessionTable[i].ackNumber = HTONL((tcpHeader->seqNumber)) + dataLen;

}else{ //Neu dataLen == 0 thi cung tang so tuan tu len 1

tcpSessionTable[i].ackNumber = HTONL((tcpHeader->seqNumber))+1;

}//tcpSessionTable[i].seqNumber = HTONL((tcpHeader-

>ackNumber));TCPPackedSend(&tcpSessionTable[i], (TCP_FIN_FLAG|

TCP_ACK_FLAG), 0, tcpData);break;

//Truong hop dang o trang thai FIN WAIT 1 (da truyen du lieu xong,

// san sang dong ket noi va da gui di co FIN va dang cho ACK)case(TCP_STATE_FIN_WAIT1):

//if we receive FINtcpSessionTable[i].ackNumber = HTONL((tcpHeader-

>seqNumber))+1;if (tcpHeader->Flags == TCP_FIN_FLAG){//Neu chi nhan

duoc co FIN//Chuyen sang trang thai CLOSING va gui ACKtcpSessionTable[i].sesState =

TCP_STATE_CLOSING;TCPPackedSend(&tcpSessionTable[i],

(TCP_ACK_FLAG), 0, tcpData);//tcpSessionTable[i].seqNumber++;#ifdef TCP_DEBUGprintf("Closing\n\r");#endif

}else if(tcpHeader->Flags == (TCP_FIN_FLAG | TCP_ACK_FLAG)){//Neu nhan dong thoi FIN va ACK

//Chuyen sang trang thai TIME_WAIT va gui ACK// nhung o day do chua co timer nen ta chuyen

luon sang dong ket noiif (HTONL((tcpHeader->ackNumber)) ==

tcpSessionTable[i].seqNumber){//TCPPackedSend(&tcpSessionTable[i],

(TCP_ACK_FLAG), 0, tcpData);TCPCloseSession(i);#ifdef TCP_DEBUGprintf("End\n\r");#endif

}else{ //Neu khong dung ack cho thong bao FIN//Chuyen sang cho co ACK cuoi cungtcpSessionTable[i].sesState =

TCP_STATE_LAST_ACK;

#ifdef TCP_DEBUGprintf("Last ack\n\r");#endif

}//Gui xac nhan cho co FINTCPPackedSend(&tcpSessionTable[i],

(TCP_ACK_FLAG), 0, tcpData);//tcpSessionTable[i].seqNumber++;

}else if(tcpHeader->Flags == TCP_ACK_FLAG){ //Neu chi nhan duoc ACK

//Chuyen sang trang thai FIN WAIT2tcpSessionTable[i].sesState =

TCP_STATE_FIN_WAIT2;#ifdef TCP_DEBUGprintf("Fin wait 2\n\r");#endif

}break;

//Neu dang o trang thai FIN WAIT 2 (san sang dong ket noi va gui co FIN,

// phia ben kia da xac nhan nhung van chua san sang dong ket noi

case(TCP_STATE_FIN_WAIT2)://Neu nhan duoc co FINif (tcpHeader->Flags & TCP_FIN_FLAG){

if(dataLen){tcpSessionTable[i].ackNumber =

HTONL((tcpHeader->seqNumber))+dataLen;}else{

tcpSessionTable[i].ackNumber = HTONL((tcpHeader->seqNumber))+1;

}//FIN -> goto TIMED WAITtcpSessionTable[i].sesState =

TCP_STATE_TIMED_WAIT;TCPPackedSend(&tcpSessionTable[i],

(TCP_ACK_FLAG), 0, tcpData);//Chua co timer thi dong ket noi o day luonTCPCloseSession(i);#ifdef TCP_DEBUGprintf("End\n\r");#endif

}break;

case(TCP_STATE_TIMED_WAIT):break;

case(TCP_STATE_CLOSING):tcpSessionTable[i].sesState = TCP_STATE_TIMED_WAIT;break;

default:TCPCloseSession(i);

}//we must set timed wait TTL here because timed wait is not packet

triggeredif (tcpSessionTable[i].sesState == TCP_STATE_TIMED_WAIT){

tcpSessionTable[i].timeOut = 5; //5 seconds timeout}

return;}//--------------------------------------------------------------------------------------

Hai hàm này khiếp quá, nhìn vô thấy oải rồi, từ từ giải thích, không hiểu sao hồi xưa mình lại viết phức tạp như vậy.

Hay là mọi người cứ từ từ nghiên cứu, chỗ nào không hiểu thì cứ hỏi . Giờ nghĩ tới viết giải

thích cho code thấy nản quá. Viết giải thích cho code có khi còn khó hơn viết code .

Giao thức UDP:Tiếp theo, ta chuyển sang giao thức UDP, trong 3 giao thức ở lớp này (TCP, UDP và ICMP) thì UDP là giao thức đơn giản nhất, dễ viết nhất.Trong chồng giao thức IP, nhiệm vụ của UDP là khi nhận 1 segment dữ liệu từ ứng dụng gửi xuống, nó sẽ gửi đi ngay đến địa chỉ IP và port được yêu cầu mà không cần đánh số thứ tự, bắt tay thiết lập kết nối hay thậm chí không quan tâm đến việc dữ liệu đó có đến được đích hay không.Header của giao thức UDP rất đon giản: chỉ gồm có 4 trường: port nguồn, port đích, chiều dài gói tin và checksum.

Trong thực tế, UDP được sử dụng để gửi đi các dữ liệu mà yêu cầu về độ trễ nhỏ quan trọng hơn yêu cầu về độ tin cậy. Ví dụ như dữ liệu của các video stream, audio stream, các báo hiệu nhanh, hay đơn giản là tương tác trong các game online.

Vậy ta mở file “packet.h” thêm khai báo header UDP vào:Code://--------------------------------------------------------------------------------------//Cau truc UDP headerstruct ntUDPHeader{

unsigned int srcPort;unsigned int desPort;unsigned int Len;unsigned int Checksum;

};#define UDP_HEADER_LEN 8

Sau đó tạo file source và header tương ứng:“udp.c”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM

//----------------------------------------------------------------------------#include "packet.h"#include "ip.h"#include "uart.h"

“udp.h”Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef UDP_H#define UDP_H//----------------------------------------------------------------------------

#endif //UDP_H

Ta chỉ viết 2 hàm cho giao thức UDP:Hàm gửi gói tin bằng UDP:Code://----------------------------------------------------------------------------//Ham gui di mot goi UDPvoid udpSend(unsigned long dstIp, unsigned int dstPort, unsigned int len, unsigned char* udpData){

struct ntUDPHeader* udpHeader;udpHeader = (struct ntUDPHeader*)(udpData - UDP_HEADER_LEN);len += UDP_HEADER_LEN;udpHeader->desPort = HTONS(dstPort);udpHeader->srcPort = HTONS(dstPort);udpHeader->Len = HTONS(len);udpHeader->Checksum = 0;ipSend(dstIp, IP_PROTO_UDP, len, (unsigned char*)udpHeader);

}

Hàm xử lý gói tin nhận được:Code://--------------------------------------------------------------------------------------//Ham xu ly goi UDP nhan duoc, duoc goi boi ham xu ly goi IP (IPProcess)// Hien chua co ung dung chay UDP nen ham nay trongvoid UDPProcess(unsigned int len, struct ntIPHeader* packet){

dhcpIn((len - IP_HEADER_LEN - UDP_HEADER_LEN), (struct netDhcpHeader*)((char*)packet - IP_HEADER_LEN - UDP_HEADER_LEN));

#ifdef NET_DEBUGprintf("NetStack UDP/IP Rx Dummy Handler\r\n");#endif

}

Vì trong project này, chúng ta chỉ sử dụng UDP cho 1 ứng dụng duy nhất là DHCP nên ở đây ta chỉ gọi hàm dhcpIn để xử lý. Hàm này ta vẫn chưa viết nhé. Nếu ta còn dùng UDP cho các giao thức khác thì phức tạp hơn 1 tý, tuy nhiên vẫn đơn giản hơn nhiều so với TCP.Tiếp theo sẽ là DHCP và HTTP, xong cái này là các bạn có thể truy cập web trên mạch được rồi

(nếu có mạch ).

Đã built xong code đến phần Ping được. Trong quá trình làm theo anh Tâm có vài điểm anh Tâm nói chưa rõ lắm nên hôm nay Huy port lên phần code mới làm xong và biên dịch OK.Anh Tâm có nhầm một chỗ như sau :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) thay bằng ethernet.cCode:unsigned char eth_got_frame = 0;volatile unsigned int time_watchdog;

Và ntAVRnet.c cần include như thế này<#include <avr/io.h>#include "ntAVRnet.h"#include "ip.h"extern struct ipConfig IpMyConfig;>

Có gì sai anh Tâm chỉnh dùm nha. Tối nay về nạp vào mạch rồi Ping thôi. keke

A, đúng rồi đó, bạn tienhuypro đã phát hiện rất chính xác, phải là ethernet.c chứ không phải enc28j60.c

Giải thích cho 2 hàm chính trong giao thức TCP:Hàm TCPPackedSend:Code:void TCPPackedSend(struct tcpSession *pSession, unsigned char Flags, unsigned int len, unsigned char *dataBuffer)

Trong này:- pSession là con trỏ, trỏ đến struct tcpSession trong bảng TCP Session Table tương ứng với phiên kết nối TCP mà ta muốn gửi gói tin đi (trong phần lập trình giao thức TCP này, ta hỗ trợ việc AVR đồng thời theo dõi và truyền data với nhiều kết nối (nhiều client) cùng lúc).- Flags là các cờ tương ứng mà ta muốn set trong header TCP- len: chiều dài buffer dữ liệu ứng dụng.- dataBuffer: buffer chứa dữ liệu ứng dụng.Cấu trúc frame dữ liệu mà ENC28J60 sẽ gửi đi như sau:

Lúc này, dataBuffer đang trỏ đến phần Application Data trong frame trên, biến len đang là chiều dài phần data này.Khai báo các biến sử dụng trong hàm:Code:

unsigned int tmp;unsigned long checksum32;

Lúc này, trên buffer (dataBuffer) đã có dữ liệu ứng dụng (phần Application Data), nhiệm vụ của giao thức TCP là phải xây dựng header TCP cho frame dữ liệu này.Tiếp theo, ta khai báo 2 con trỏ, trỏ đến vùng IP Header và TCP Header (mới khai báo, chưa gán địa chỉ):Code:

struct ntTCPHeader* tcpHeader;struct ntIPHeader* ipHeader;

Nếu là đang trong giai đoạn thiết lập kết nối (cờ SYN được set) thì TCP Header sẽ có thêm trường Option, chứa giá trị MSS (Max Segment Size), ta ghi luôn vào vùng buffer dành cho Application Data, vì khi SYN thì vùng này không có (các bản tin thiết lập kết nối không chứa dữ liệu ứng dụng).Code:

if(Flags & TCP_SYN_FLAG){//Option datadataBuffer[0] = 0x02;dataBuffer[1] = 0x04;dataBuffer[2] = (MAX_SEGMENT_SIZE >> 8) & 0xff;dataBuffer[3] = MAX_SEGMENT_SIZE & 0xff;dataBuffer[4] = 0x01;dataBuffer[5] = 0x03;dataBuffer[6] = 0x03;dataBuffer[7] = 0x00;

}

Bây giờ ta dịch lui con trỏ dataBuffer 1 đoạn bằng chiều dài TCP Header và trỏ tcpHeader đến phần đầu đoạn này.Code:

dataBuffer -= TCP_HEADER_LEN;tcpHeader = (struct ntTCPHeader*)dataBuffer;

Điền các trường cho TCP Header:- Source port và Des port lấy từ thông tin về session tương ứng:Code:

tcpHeader->srcPort = HTONS(pSession->srcPort);tcpHeader->desPort = HTONS(pSession->desPort);

- Số tuần tự cũng vậy:Code:

tcpHeader->seqNumber = HTONL(pSession->seqNumber);

Sau đó ta phải tăng số tuần tự lên tùy theo chiều dài dữ liệu:Code:

pSession->seqNumber = pSession->seqNumber + len;

Lưu ý (chỗ này lúc viết code mình bị sai, debug mất cả buổi tối): tuy các bản tin SYN và FIN không chứa dữ liệu, nó vẫn được xem là có chiều dài dữ liệu 1 byte, phải tăng số tuần tự lên 1 đối với các bản tin này:Code:

if(Flags & (TCP_FIN_FLAG|TCP_SYN_FLAG))(pSession->seqNumber)++;

Số ACK:Code:

tcpHeader->ackNumber = HTONL(pSession->ackNumber);

Nếu có cờ SYN, như đã nói trên Header TCP sẽ có trường Option chiều dài 8 byte, do đó ta phải ghi giá trị tương ứng cho trường Offset, và tăng biến len lên tương ứng:Code:

if(Flags & TCP_SYN_FLAG){tcpHeader->Offset = (0x07<<4);len += (TCP_HEADER_LEN + 8);

Nếu không, chiều dài TCP Header là mặc định (đã định nghĩa TCP_HEADER_LEN)Code:

}else{tcpHeader->Offset = (0x05<<4);len += TCP_HEADER_LEN;

}

Ghi giá trị các cờCode:

tcpHeader->Flags = Flags;

Giá trị trường WindowCode:

tcpHeader->Window = HTONS(pSession->srcWin);//((NETSTACK_BUFFERSIZE-20-14));

Tạm thời gán checksum = 0 (ta sẽ tính TCP checksum sau)Code:

tcpHeader->Checksum = 0;

Con trỏ khẩn không sử dụng:Code:

tcpHeader->UrgentPtr = 0x0000;

Trỏ đến biến con trỏ ipHeader đến vùng IP Header trên buffer:Code:

ipHeader = (struct ntIPHeader*)(dataBuffer-IP_HEADER_LEN);

Gán tạm thời 1 số giá trị cần thiết cho IP Header để tính checksum:Code:

ipHeader->srcIPAddr = HTONL(ipGetConfig()->ip);ipHeader->desIPAddr = HTONL(pSession->desIP);ipHeader->Checksum = HTONS(len);ipHeader->TTL = 0x00;ipHeader->Protocol = IP_PROTO_TCP;

Ở đây các bạn cần tìm hiểu thêm cách tính checksum TCP, nó không chỉ tính checksum phần TCP Header và dữ liệu mà còn tính thêm 1 số trường của IP trong 1 header IP giả, do đó ta phải gán tạm các trường này lên buffer thì mới tính được TCP checksum, còn thật ra Header IP xuống giao thức IP sẽ được xây dựng lại (hàm ipSend).Tính checksum và ghi vào trường checksum của Header TCP

Code:checksum32 = 0;tmp = len + 12;tmp = checksum (((unsigned char *)ipHeader+8), tmp, checksum32);tcpHeader->Checksum = HTONS(tmp);

Gọi hàm ipSend để yêu cầu giao thức IP gửi gói tin đi.Code:

ipSend(pSession->desIP, IP_PROTO_TCP, len, (unsigned char *)tcpHeader);

Hàm TCPProcess:Hàm này dài quá nên chỉ giải thích cấu trúc hàm cho các bạn dễ đọc thôi:Khai báo các biến sử dụng:Code:

unsigned char i,ipHeaderLen,tcpHeaderLen;unsigned int dataLen;unsigned long tmp;struct ntIPHeader* ipHeader;struct ntTCPHeader* tcpHeader;unsigned char *tcpData;

Trỏ các biến con trỏ header IP và TCP đến vùng header tương ứng. Lúc này trên buffer đã có đầy đủ frame dữ liệu đọc từ ENC28J60.Code:

//Khoi tao cac co tro den Header IP va TCPipHeader = (struct ntIPHeader*)(buffer);ipHeaderLen = ((ipHeader->verHdrLen) & 0x0F) << 2;//tcpHeader = (struct ntTCPHeader*)(buffer+ipHeaderLen);tcpHeaderLen = ((tcpHeader->Offset) & 0xF0) >> 2;//tcpData = (buffer+ipHeaderLen+tcpHeaderLen);dataLen = HTONS(ipHeader->Len) - (ipHeaderLen + tcpHeaderLen);

Sau khi đọc được địa chỉ IP nguồn của host gửi và port nguồn, port đích, ta tìm kiếm trong TCP session table phiên TCP có sẵn tương ứng với gói tin này, nếu chưa tồn tại thì khởi tạo session mới:Code:

if(i == TCP_MAX_SESSION){ //Neu khong co 1 phien TCP dang ton tai cho goi nay

//Tim 1 phien dang o trang thai LISTEN (doi ket noi) cho local port nay

for(i=0; i < TCP_MAX_SESSION; i++){if(tcpSessionTable[i].sesState == TCP_STATE_LISTEN){

if(tcpSessionTable[i].srcPort == HTONS((tcpHeader->desPort))){ //If matched local port

//Cap nhat remote port va remote IPtcpSessionTable[i].desPort =

HTONS((tcpHeader->srcPort));tcpSessionTable[i].desIP =

HTONL((ipHeader->srcIPAddr));//Dong thoi tao ra 1 session moi de cho

ket noi khac den local port nay

TCPCreateSession(tcpSessionTable[i].srcPort,tcpSessionTable[i].appDataIn);break;

}}

}}if(i == TCP_MAX_SESSION){

#ifdef TCP_DEBUGprintf("No TCP session found\r\n");#endifreturn; //Neu khong co phien TCP nao danh cho goi nay thi thoat

ra}#ifdef TCP_DEBUGprintf("TCP session found: %d\r\n",i);#endif

Ta reset biến Timeout mỗi khi nhận được gói tin. Biến này nhằm phát hiện và hủy bỏ các phiên TCP bị “treo” một thời gian dài mà không nhận được dữ liệu.Code:

tcpSessionTable[i].timeOut = TCP_TIMEOUT; //Reset lai gia tri Time out

Nếu cờ RST=1 (Reset). Đóng kết nối.Code:

if ((tcpHeader->Flags) & TCP_RST_FLAG){//Chap nhan dong ket noiTCPCloseSession(i);return;

}

Tiếp theo, ta phải xử lý theo lưu đồ trạng thái của TCP. Ta dùng 1 cấu trúc switch…case để xử lý tùy vào trạng thái hiện tại của kết nối:Code:

switch (tcpSessionTable[i].sesState){//Neu la trang thai doi ket noi: TCP_STATE_LISTENcase(TCP_STATE_LISTEN):

break;//Neu la trang thai TCP_STATE_SYN_RECEIVEDcase(TCP_STATE_SYN_RECEIVED):

break;//Truong hop ket noi da duoc thiet lapcase(TCP_STATE_ESTABLISHED):

break;//Neu la trang thai doi LAST_ACK (2 phia deu san sang dong ket

noi, dang doi xac nhan ack cuoi cung)case(TCP_STATE_LAST_ACK):

break;//Truong hop ngat ket noi thu dong, da nhan co FIN tu remote

host va xac nhancase(TCP_STATE_CLOSE_WAIT):

break;//Truong hop dang o trang thai FIN WAIT 1 (da truyen du lieu

xong,// san sang dong ket noi va da gui di co FIN va dang cho ACK)case(TCP_STATE_FIN_WAIT1):

break;

//Neu dang o trang thai FIN WAIT 2 (san sang dong ket noi va gui co FIN,

// phia ben kia da xac nhan nhung van chua san sang dong ket noi

case(TCP_STATE_FIN_WAIT2):break;

case(TCP_STATE_TIMED_WAIT):break;

case(TCP_STATE_CLOSING):break;

default:TCPCloseSession(i);

}

Trong mỗi trạng thái, ta xử lý thông tin trong header TCP và chuyển trạng thái tương ứng. Mọi

người tự đọc nhé .

Bài 6: Các giao thức lớp ứng dụng

Giao thức DHCP:- DHCP là một giao thức được sử dụng để phân phối động các tham số cấu hình TCP/IP cho các máy tính. Một DHCP server có thể cho client DHCP một số thiết lập TCP/IP, như là địa chỉ IP, subnet mask, và máy chủ DNS.- Mỗi client sẽ nhận một hợp đồng thuê địa chỉ trong thời gian hạn định. Nếu client không còn sử dụng địa chỉ khi hợp đồng thuê hết hạn, địa chỉ đó có thể cấp phát cho các client khác.Cơ chế làm việc của DHCP: gồm có 4 bản tin chính:+ DHCPDISCOVER: DHCP client khởi tạo tiến trình bằng cách quảng bá một gói tới cổng UDP 68 (sử dụng cho máy chủ BOOTP và DHCP). Gói đầu tiên này được gọi là bản tin DHCP Discover, nó sẽ yêu cầu bất cứ DHCP server nào nhận được gói thực hiện việc cấu hình. Gói DHCP discover gồm rất nhiều trường, nhưng một vùng quan trọng nhất chứa địa chỉ vật lý của DHCP client.

+ DHCPOFFER: Một DHCP server được cấu hình cung cấp hợp đồng địa chỉ cho mạng mà client cư trú sẽ đáp ứng lại một gói tên là DHCP offer và gửi nó dưới dạng quảng bá tới máy đưa ra DHCP discover. Thông điệp quảng bá này được gửi tới cổng UDP 67 và bao gồm địa chỉ vật lý của client, địa chỉ vật lý và địa chỉ IP của DHCP server, cũng như giá trị địa chỉ IP và subnet mask cung cấp cho DHCP client.

+ DHCPREQUEST: Client chọn một DHCP offer, tạo một gói DHCP request và quảng bá gói này. Gói DHCP request này bao gồm địa chỉ IP của server phát ra DHCP offer và địa chỉ vật lý của DHCP client. DHCP request này thực hiện hai việc:. Báo cho DHCP server được chọn rằng nó yêu cầu một địa chỉ IP.. Thông báo cho các DHCP server khác là DHCP offer của chúng không được chấp nhận.

+ DHCPACK: Khi DHCP server được chọn nhận được DHCP request, nó sẽ trả lời bắng gói DHCP ack. DHCP ack bao gồm địa chỉ IP và subnetmask cho DHCP client. Ngoài các thông tin về địa chỉ IP, DHCP client có thể nhận thêm các thông tin cấu hình như địa chỉ IP của gateway, máy chủ DNS, ...

em cũng đang làm về pic18 +enc28j60em biên dịch chương trình demo của microchip thì ok.nhưng khi em tạo 1 project khác và copy toàn bộ code của microchip sang thì khi biên dịch nó lại mắc lỗi như này: Error [1027] unable to locate 'TCPIP Stack/TCPIP.h' tại sao cái file "TCPIP Stack/TCPIP.h" ko có trong khi mọi file *.h trong Microchip stack đều include nó hết.khi dịch bản demo thì lại đượcmà khi tự tạo 1 project mới thì khi dịch lại báo lỗi : Error [1027] unable to locate 'TCPIP Stack/TCPIP.h' cám ơn thầy!p/s:nếu có sai toppic thì mọi người thông cảm nhé vì nếu ai mà làm với pic + enc28j60 cũng sẽ mắc lỗi

này

File "TCPIP.h" nằm trong thư mục "Include/TCPIP Stack/" cùng với các file header khác mà. Có thể là em chép thiếu thư mục Include hoặc không chỉ định thư mục chứa Header (Include Diectory) cho Project mới thôi.

Thưa thầy ,em xin hỏi mấy câu :MAC gateway là mình lấy từ modem ADSL?.Em gửi gói arp request đến địa chỉ ip 192.168.1.1 (gateway) để hỏi địa chỉ MAC của nó có được kô.Em thấy trong code của thầy thì gói

arp,ethernet,ip chỉ có phần header.Vậy có phải chỉ có lớp ứng dụng mới chứa data?.Em mới viết code đến đây thôi.Em cảm ơn thầy nhiều.Trong mạng LAN dùng Modem ADSL thì gateway chính là modem ADSL, do vậy khi cần biết địa chỉ MAC của nó thì đương nhiên phải hỏi nó rồi, nhưng ở đây ta chưa biết địa chỉ MAC của nó nên ta phải gửi Broadcast (địa chỉ IP đích = 192.168.1.1, MAC đích = FF:FF:FF:FF:FF:FF).

Gateway chính là Router kết nối giữa mạng LAN với bên ngoài. Trong trường hợp này chính là ADSL Router.

Trong phần giao thức ARP đã post, thực ra tôi đã lách luật khi không gửi ARP request khi chưa biết địa chỉ MAC mà điền luôn địa chỉ MAC broadcast vào gói tin cần gửi đi (tôi có đố các bạn phát hiện ra chỗ lách luật này mà không thấy ai phản hồi). rptdnmqs có biết tại sao phải lách luật như vậy không?

- Data của ứng dụng chứa trong phần data của gói TCP hay UDP nằm ngay sau Header TCP/UDP.- Gói IP thì nó xem toàn bộ gói TCP/UDP là data của nó, và Header của gói là Header IP.- Tương tự, frame Ethernet coi toàn bộ gói IP là data của nó, Header là Header của frame ethernet.- Vậy gói của giao thức nào cũng có data hết, có điều data của nó chính là toàn bộ gói của giao thức lớp trên.- Còn dữ liệu ứng dụng trong gói TCP/UDP đương nhiên là chỉ do lớp ứng dụng tạo ra mà thôi.

Lỡ hẹn đã lâu, hôm nay tiếp tục với giao thức DHCP, còn thằng này với HTTP nữa là xong.

Trước hết, ta lại tạo 2 file source và header:"dhcp.c"Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include "packet.h"#include "ethernet.h"#include "ip.h"#include "udp.h"#include "dhcp.h"#include "uart.h"#include "ntAVRnet.h"//

"dhcp.h"Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef DHCP_H#define DHCP_H

#include "packet.h"//#define DHCP_DEBUG

Trong file Header, ta khai báo các Header BOOTP và DHCP:Code://----------------------------------------------------------------------------/// BOOTP Headerstruct ntBootpHeader{

unsigned char opcode; //Message op-code / message typeunsigned char hwaddrtype; //Hardware address type

(Ethernet=1)unsigned char hwaddrlen; //Hardware address length

(Ethernet=6 byte MAC addr)unsigned char hops; //hop count (client set to zero)unsigned long transid; //Transaction ID (randomly

chosen by client, must remain same)unsigned int secs; //Seconds elapsed since DHCP

negotiation began (filled by client)unsigned int flags; //Flagsunsigned long clipaddr; //Client IP address (filled only

if already bound, renewing, or rebinding)unsigned long yoipaddr; //Your IP address (client)unsigned long seipaddr; //Server IP addressunsigned long gwipaddr; //Gateway IP addressunsigned char clhwaddr[16]; //Client Hardware Addressunsigned char sename[64]; //Server Host Nameunsigned char file[128]; //Boot file name (null-term

string)};//#define BOOTP_HEADER_LEN 236 //length of BOOTP header not including options//#define BOOTP_OP_BOOTREQUEST 1 //BOOTP Request operation (message from client to server)#define BOOTP_OP_BOOTREPLY 2 //BOOTP Reply operation (message from server to client)//#define BOOTP_HTYPE_ETHERNET 1 //Hardware type for ethernet protocol#define BOOTP_HLEN_ETHERNET 6 //Length of ethernet MAC address//----------------------------------------------------------------------------struct netDhcpHeader{

struct ntBootpHeader bootp; //BOOTP headerunsigned long cookie; //magic cookie valueunsigned char options[1]; //DHCP options

};//#define DHCP_HEADER_LEN 240 //length of DHCP header not including options

Tiếp theo ta định nghĩa 1 số giá trị hằng được qui định cho các trường trong header:Code://Code for DHCP option field#define DHCP_OPT_PAD 0 //token padding value (make be skipped)

#define DHCP_OPT_NETMASK 1 //subnet mask client should use (4 byte mask)#define DHCP_OPT_ROUTERS 3 //routers client should use (IP addr list)#define DHCP_OPT_TIMESERVERS 4 //time servers client should use (IP addr list)#define DHCP_OPT_NAMESERVERS 5 //name servers client should use (IP addr list)#define DHCP_OPT_DNSSERVERS 6 //DNS servers client should use (IP addr list)#define DHCP_OPT_HOSTNAME 12 //host name client should use (string)#define DHCP_OPT_DOMAINNAME 15 //domain name client should use (string)#define DHCP_OPT_REQUESTEDIP 50 //IP address requested by client (IP address)#define DHCP_OPT_LEASETIME 51 //DHCP Lease Time (uint32 seconds)#define DHCP_OPT_DHCPMSGTYPE 53 //DHCP message type (1 byte)#define DHCP_OPT_SERVERID 54 //Server Identifier (IP address)#define DHCP_OPT_PARAMREQLIST 55 //Paramerter Request List (n OPT codes)#define DHCP_OPT_RENEWALTIME 58 //DHCP Lease Renewal Time (uint32 seconds)#define DHCP_OPT_REBINDTIME 59 //DHCP Lease Rebinding Time (uint32 seconds)#define DHCP_OPT_CLIENTID 61 //DHCP Client Identifier#define DHCP_OPT_END 255 //token end value (marks end of options list)//Code for DHCP message type#define DHCP_MSG_DHCPDISCOVER 1 //DISCOVER is broadcast by client to solicit OFFER from any/all DHCP servers.#define DHCP_MSG_DHCPOFFER 2 //OFFER(s) are made to client by server to offer IP address and config info.#define DHCP_MSG_DHCPREQUEST 3 //REQUEST is made my client in response to best/favorite OFFER message.#define DHCP_MSG_DHCPDECLINE 4 //DECLINE may be sent by client to server to indicate IP already in use.#define DHCP_MSG_DHCPACK 5 //ACK is sent to client by server in confirmation of REQUEST, contains config and IP.#define DHCP_MSG_DHCPNAK 6 //NAK is sent to client by server to indicate problem with REQUEST.#define DHCP_MSG_DHCPRELEASE 7 //RELEASE is sent by client to server to relinquish DHCP lease on IP address, etc.#define DHCP_MSG_DHCPINFORM 8 //INFORM is sent by client to server to request config info, IP address configured locally.

Định nghĩa port UDP được qui định dành cho giao thức BOOTP và DHCP, và giá trị timeout (số giây chờ trả lời sau khi gửi DHCP discover), số lần cố gắng nhận địa chì IP thông qua DHCP:Code:#define DHCP_UDP_SERVER_PORT 67 //UDP port where DHCP requests should be sent#define DHCP_UDP_CLIENT_PORT 68 //UDP port clients will receive DHCP replies//#define DHCP_TIMEOUT 10#define DHCP_RETRIES 3

Bây giờ ta mở file source và bắt đầu viết code:Trước hết ta khai báo các biến sử dụng :Code://--------------------------------------------------------------------------------------unsigned long DhcpServerIP;unsigned long DhcpTransactID;unsigned long DhcpLeaseTime;unsigned char macaddr[6];//unsigned char DhcpTimeout;unsigned char DhcpRetries;

Viết hàm khởi tạo giao thức DHCP:Code://--------------------------------------------------------------------------------------//Ham khoi tao cac thong so ban dau cho DHCPvoid dhcpInit(void){

ethGetMacAddress(macaddr);DhcpTransactID = *((unsigned long*)&macaddr[0]);DhcpLeaseTime = 0;DhcpTimeout = 1;DhcpRetries = DHCP_RETRIES;

}

Hàm ghi giá trị vào trường option của DHCP, mỗi trường option sẽ bắt đầu bằng 1 byte option code, tiếp đến là 1 byte chiều dài của giá trị option, theo sau là các byte giá trị của option:Code://--------------------------------------------------------------------------------------//Ham set cac option cua DHCPunsigned char* dhcpSetOption(unsigned char* options, unsigned char optcode, unsigned char optlen, void* optvalptr){

*options++ = optcode;*options++ = optlen;while(optlen--){

*options++ = *(unsigned char*)optvalptr++;}*options = DHCP_OPT_END;return options;

}

Hàm đọc giá trị từ trường option vào 1 biến:Code://--------------------------------------------------------------------------------------//Ham lay cac option cua DHCPunsigned char dhcpGetOption(unsigned char* options, unsigned char optcode, unsigned char optlen, void* optvalptr){

unsigned char i;for (;;)

{if(*options == DHCP_OPT_PAD)

options++;else if(*options == DHCP_OPT_END)

break;else if(*options == optcode){

optlen = ((optlen<*(options+1))?(optlen):(*(options+1)));

for(i=0; i<optlen; i++)*(((uint8_t*)optvalptr)+i) = *(options+i+2);

return *(options+1);}else{

options++;options+=*options;options++;

}}return 0;

}

Hàm xuất ra cổng serial Header của bản tin (dùng cho mục đích debug) :Code://--------------------------------------------------------------------------------------#ifdef DHCP_DEBUG//Ham de in Header goi DHCP (de debug)void dhcpPrintHeader(struct netDhcpHeader* packet){

printf("DHCP Packet:\r\n");// print opprintf("Op : ");switch(packet->bootp.opcode){case BOOTP_OP_BOOTREQUEST: printf("BOOTREQUEST"); break;case BOOTP_OP_BOOTREPLY: printf("BOOTREPLY"); break;default: printf("UNKNOWN");

break;}printf("\n\r");// print transaction IDprintf("XID : 0x"); /*rprintfu32(packet->bootp.transid);*/

printf("\n\r");// print client IP addressprintf("ClIpAddr: "); ipPrintAddr(HTONL(packet->bootp.clipaddr));printf("\n\r");// print 'your' IP addressprintf("YrIpAddr: "); ipPrintAddr(HTONL(packet->bootp.yoipaddr));printf("\n\r");// print server IP addressprintf("SvIpAddr: "); ipPrintAddr(HTONL(packet->bootp.seipaddr));printf("\n\r");// print gateway IP addressprintf("GwIpAddr: "); ipPrintAddr(HTONL(packet->bootp.gwipaddr));printf("\n\r");

// print client hardware addressprintf("ClHwAddr: "); ethPrintAddr((struct ntEthAddr*)packet-

>bootp.clhwaddr); printf("\n\r");}#endif

Hàm gửi đi bản tin DHCP discover :Code://--------------------------------------------------------------------------------------//Ham gui di mot ban tin DHCP discover de tim kiem DHCP servervoid dhcpDiscover(void){

struct netDhcpHeader* packet;unsigned long val;unsigned char* optptr;

packet = (struct netDhcpHeader*)(ethGetBuffer() + ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN);

//packet->bootp.opcode = BOOTP_OP_BOOTREQUEST;packet->bootp.hwaddrtype = BOOTP_HTYPE_ETHERNET;packet->bootp.hwaddrlen = BOOTP_HLEN_ETHERNET;packet->bootp.clipaddr = HTONL(ipGetConfig()->ip);packet->bootp.yoipaddr = HTONL(0l);packet->bootp.seipaddr = HTONL(0l);packet->bootp.gwipaddr = HTONL(0l);ethGetMacAddress(&packet->bootp.clhwaddr[0]);packet->bootp.transid = DhcpTransactID;packet->bootp.flags = HTONS(1);//packet->cookie = 0x63538263;val = DHCP_MSG_DHCPDISCOVER;optptr = dhcpSetOption(packet->options, DHCP_OPT_DHCPMSGTYPE, 1,

&val);dhcpSetOption(optptr, DHCP_OPT_CLIENTID, 6, macaddr);#ifdef DHCP_DEBUGprintf("DHCP: Sending Query\r\n");dhcpPrintHeader(packet);#endifudpSend(0xFFFFFFFF, DHCP_UDP_SERVER_PORT, DHCP_UDP_CLIENT_PORT,

DHCP_HEADER_LEN+3+1+8, (unsigned char*)packet);}

Hàm gửi bản tin DHCP request để đáp ứng lại cho DHCP offer :Code://--------------------------------------------------------------------------------------//Ham gui di mot ban tin DHCP request de yeu cau nhan dia chi IPvoid dhcpRequest(struct netDhcpHeader* packet, unsigned long serverid){

unsigned char* optptr;unsigned long val;

packet->bootp.opcode = BOOTP_OP_BOOTREQUEST; // request typeval = DHCP_MSG_DHCPREQUEST;optptr = dhcpSetOption(packet->options, DHCP_OPT_DHCPMSGTYPE, 1,

&val);

optptr = dhcpSetOption(optptr, DHCP_OPT_CLIENTID, 6, macaddr);optptr = dhcpSetOption(optptr, DHCP_OPT_SERVERID, 4, &serverid);optptr = dhcpSetOption(optptr, DHCP_OPT_REQUESTEDIP, 4, &packet-

>bootp.yoipaddr);((unsigned char*)&val)[0] = DHCP_OPT_NETMASK;((unsigned char*)&val)[1] = DHCP_OPT_ROUTERS;((unsigned char*)&val)[2] = DHCP_OPT_DNSSERVERS;((unsigned char*)&val)[3] = DHCP_OPT_DOMAINNAME;optptr = dhcpSetOption(optptr, DHCP_OPT_PARAMREQLIST, 4, &val);packet->bootp.yoipaddr = HTONL(0l);#ifdef DHCP_DEBUGprintf("DHCP: Sending request in response to offer\r\n");#endifudpSend(0xFFFFFFFF, DHCP_UDP_SERVER_PORT, DHCP_UDP_CLIENT_PORT,

DHCP_HEADER_LEN+3+6+6+6+8+1, (unsigned char*)packet);}

Hàm xử lý gói DHCP nhận được, hàm này sẽ được gọi bởi giao thức UDP:Code://--------------------------------------------------------------------------------------//Ham xu ly mot goi DHCP nhan duocvoid dhcpIn(unsigned int len, struct netDhcpHeader* packet){

unsigned char msgtype;unsigned long sid;unsigned long netmask;unsigned long gateway;unsigned long val;//#ifdef DHCP_DEBUGdhcpPrintHeader(packet);#endif//if((packet->bootp.opcode != BOOTP_OP_BOOTREPLY) || (packet-

>bootp.transid != DhcpTransactID))return;

//dhcpGetOption(packet->options, DHCP_OPT_DHCPMSGTYPE, 1, &msgtype);#ifdef DHCP_DEBUGprintf("DHCP: Received msgtype = %d\r\n", msgtype);#endif//if(msgtype == DHCP_MSG_DHCPOFFER){

dhcpGetOption(packet->options, DHCP_OPT_SERVERID, 4, &sid);#ifdef DHCP_DEBUGprintf("DHCP: Got offer from server ");

ipPrintAddr(HTONL(sid)); printf("\n\r");#endifdhcpRequest(packet, sid);

}//else if(msgtype == DHCP_MSG_DHCPACK){

dhcpGetOption(packet->options, DHCP_OPT_NETMASK, 4, &val);netmask = HTONL(val);

//dhcpGetOption(packet->options, DHCP_OPT_ROUTERS, 4, &val);gateway = HTONL(val);//dhcpGetOption(packet->options, DHCP_OPT_LEASETIME, 4, &val);DhcpLeaseTime = HTONL(val);//ipSetConfig(HTONL(packet->bootp.yoipaddr), netmask, gateway);//DhcpRetries = 0;#ifdef DHCP_DEBUGprintf("DHCP: Got request ACK, bind complete\r\n");ipPrintConfig(ipGetConfig());printf("LeaseTm : %d\n\r", DhcpLeaseTime);#endif

}}

Hàm hủy bỏ địa chỉ IP hiện tại:Code://--------------------------------------------------------------------------------------//Ham release dia chi IP hien tai va xoa cac thong so cau hinh IP dang covoid dhcpRelease(void){

struct netDhcpHeader* packet;unsigned long val;unsigned char* optptr;//packet = (struct netDhcpHeader*)&ethGetBuffer()

[ETH_HEADER_LEN+IP_HEADER_LEN+UDP_HEADER_LEN];//packet->bootp.opcode = BOOTP_OP_BOOTREQUEST;packet->bootp.hwaddrtype = BOOTP_HTYPE_ETHERNET;packet->bootp.hwaddrlen = BOOTP_HLEN_ETHERNET;packet->bootp.clipaddr = HTONL(ipGetConfig()->ip);packet->bootp.yoipaddr = HTONL(0l);packet->bootp.seipaddr = HTONL(0l);packet->bootp.gwipaddr = HTONL(0l);ethGetMacAddress(&packet->bootp.clhwaddr[0]);packet->bootp.transid = DhcpTransactID;packet->bootp.flags = HTONS(1);//packet->cookie = 0x63538263;//val = DHCP_MSG_DHCPRELEASE;optptr = dhcpSetOption(packet->options, DHCP_OPT_DHCPMSGTYPE, 1,

&val);//val = HTONL(DhcpServerIP);optptr = dhcpSetOption(optptr, DHCP_OPT_SERVERID, 4, &val);//optptr = dhcpSetOption(optptr, DHCP_OPT_REQUESTEDIP, 4, &packet-

>bootp.clipaddr);//#ifdef DHCP_DEBUG

printf("DHCP: Sending Release to "); ipPrintAddr(DhcpServerIP); printf("\n\r");

#endifudpSend(DhcpServerIP, DHCP_UDP_SERVER_PORT, DHCP_UDP_CLIENT_PORT,

DHCP_HEADER_LEN+3+6+6+1, (unsigned char*)packet);ipSetConfig(0,0,0);DhcpLeaseTime = 0;

}

Hàm cập nhật các biến thời gian mà DHCP sử dụng (được gọi 1s/lần bởi timer):Code://--------------------------------------------------------------------------------------//Ham duoc goi dinh ky moi 1s de cap nhat lease time va timeout cua DHCPvoid dhcpTimer(void){

// this function to be called once per second// decrement lease timeif(DhcpLeaseTime)

DhcpLeaseTime--;if(DhcpTimeout){

DhcpTimeout--;}

}

Hàm dịch vụ DHCP, ta sẽ đưa vào vòng lặp chương trình chính:Code://--------------------------------------------------------------------------------------//Ham dich vu DHCP, duoc goi trong chuong trinh chinhvoid dhcpService(void){

if(DhcpRetries && (DhcpTimeout == 0)){DhcpRetries--;DhcpTimeout = DHCP_TIMEOUT;dhcpDiscover();

}}//--------------------------------------------------------------------------------------

Vậy là xong file source và header cho DHCP, các bạn nhớ thêm khai báo hàm vào file header

nhé .

Tiếp theo, để có thể chạy thử giao thức DHCP, chúng ta cần modify source cũ một tí để bổ sung giao thức này vào:

1-Mở file “timer.h” sửa define cho prescale của timer thành 1024 để timer chạy đúng, ngắt 1s/lần (với thạch anh 12MHz nha các bạn):Code:#define TIMER1PRESCALE TIMER_CLK_DIV1024

2-Mở file “udp.c”, sửa hàm udpSend để bổ sung source port vào trong hàm, thêm lệnh gọi hàm của giao thức DHCP vào hàm UDPProcess:Code:

//----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include "packet.h"#include "ip.h"#include "uart.h"#include "dhcp.h"//#define UDP_DEBUG//----------------------------------------------------------------------------//Ham gui di mot goi UDPvoid udpSend(unsigned long dstIp, unsigned int dstPort, unsigned int srcPort, unsigned int len, unsigned char* udpData){

struct ntUDPHeader* udpHeader;udpHeader = (struct ntUDPHeader*)(udpData - UDP_HEADER_LEN);len += UDP_HEADER_LEN;udpHeader->desPort = HTONS(dstPort);udpHeader->srcPort = HTONS(srcPort);udpHeader->Len = HTONS(len);udpHeader->Checksum = 0;ipSend(dstIp, IP_PROTO_UDP, len, (unsigned char*)udpHeader);

}//--------------------------------------------------------------------------------------//Ham xu ly goi UDP nhan duoc, duoc goi boi ham xu ly goi IP (IPProcess)// Hien chua co ung dung chay UDP nen ham nay trongvoid UDPProcess(unsigned int len, struct ntIPHeader* packet){

dhcpIn((len - IP_HEADER_LEN - UDP_HEADER_LEN), (struct netDhcpHeader*)((char*)packet + IP_HEADER_LEN + UDP_HEADER_LEN));

#ifdef UDP_DEBUGprintf("Rx UDP Packet\r\n");#endif

}//--------------------------------------------------------------------------------------

(Nhớ cập nhật khai báo hàm bên file header nha)

3-Thêm một số include (khi dịch báo lỗi thì các bạn cũng tự tìm ra mà thêm vào thôi, chỉ luôn cho đỡ mắc công kiếm):#include <avr/pgmspace.h>//// vào “tcp.h”#include "dhcp.h"//// vào “ntAVRnet.h”#include "udp.h" và #include "icmp.h" vào “ip.h”

4-Sửa hàm IPProcess, thêm các lệnh gọi hàm của giao thức UDP, TCP vào:Code:void IPProcess(unsigned int len, struct ntIPHeader* packet){

// check IP addressing, stop processing if not for me and not a broadcast

if( (HTONL(packet->desIPAddr) != ipGetConfig()->ip) &&(HTONL(packet->desIPAddr) != (ipGetConfig()->ip|ipGetConfig()-

>netmask)) &&(HTONL(packet->desIPAddr) != 0xFFFFFFFF) &&

(ipGetConfig()->ip != 0x00000000) ) return;

// handle ICMP packetif( packet->Protocol == IP_PROTO_ICMP ){

#ifdef IP_DEBUGprintf("IP->Rx: ICMP/IP packet\r\n");//icmpPrintHeader((icmpip_hdr*)packet);#endificmpIpIn((struct ntIPHeader*)packet);

}else if( packet->Protocol == IP_PROTO_UDP ){

#ifdef IP_DEBUGprintf("IP->Rx: UDP/IP packet\r\n");//debugPrintHexTable(NetBufferLen-14, &NetBuffer[14]);#endifUDPProcess(len, ((struct ntIPHeader*)packet) );////

}else if( packet->Protocol == IP_PROTO_TCP ){

#ifdef IP_DEBUGprintf("IP->Rx: TCP/IP packet\r\n");#endifTCPProcess((unsigned char *)packet,len-((packet->verHdrLen &

0x0F)<<2));////}else{

#ifdef IP_DEBUGprintf("IP->Rx: IP packet\r\n");#endif

}}//--------------------------------------------------------------------------------------

5-Mở hàm ngắt timer 1 (trong file timer.h), thêm vào các hàm cập nhật thời gian của giao thức TCP và DHCP vào:Code:TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1){

//Tai nap gia tri timer 1TCNT1 = 0xFFFF - TIMER1_INTERVAL;//Cap nhat watchdog timerif((time_watchdog++) > 120){

time_watchdog = 0; ethInit();

}Counter1s++;arpTimer();TCPCheckTimeOut();////dhcpTimer();////

}

6-Thêm vào hàm main trong file “ntAVRnet.h”Code:

int main(){

SystemInit();printf("\r\nNTTam AVR network testing with enc28j60.\r\n");printf("Initializing Network Interface and Stack\r\n");printf("Ethernet chip init\r\n");IpMyConfig.ethaddr.addr[0] = ETHADDR0;IpMyConfig.ethaddr.addr[1] = ETHADDR1;IpMyConfig.ethaddr.addr[2] = ETHADDR2;IpMyConfig.ethaddr.addr[3] = ETHADDR3;IpMyConfig.ethaddr.addr[4] = ETHADDR4;IpMyConfig.ethaddr.addr[5] = ETHADDR5;IpMyConfig.ip = IPADDRESS;IpMyConfig.netmask = NETMASK;IpMyConfig.gateway = GATEWAY;netInit(IpMyConfig.ip, IpMyConfig.netmask, IpMyConfig.gateway);PrintIPConfig();printf("Getting IP Address....\r\n");if(IpMyConfig.ip == 0x00000000){

dhcpInit();}while(1){

ethService();dhcpService();

}return 0;

}

Bây giờ nếu lúc đầu mạch của ta chưa có địa chỉ IP (IP address là 0.0.0.0) thì nó sẽ khởi tạo giao thức DHCP để nhận địa chỉ IP.Để thử, ta chỉnh lại các thông số cấu hình IP trong file “ntAVRnet.h” như sau:Code:#define IPDOT(a,b,c,d) ((unsigned long)((unsigned char)a)<<24)+((unsigned long)((unsigned char)b)<<16)+((unsigned long)((unsigned char)c)<<8)+(unsigned char)d//((a<<24)|(b<<16)|(c<<8)|(d))//#define IPADDRESS IPDOT(192,168,1,10)#define IPADDRESS IPDOT(0,0,0,0)#define NETMASK IPDOT(0,0,0,0)#define GATEWAY IPDOT(0,0,0,0)

Bây giờ ta có thể biên dịch, nối cáp mạng và mở nguồn, đợi board nhận địa chỉ IP (nhớ kết nối cổng COM để còn biết nó nhận được địa chỉ bao nhiêu nhé).Phần code này chỉ khởi động DHCP lúc mới reset mạch thôi nhé, chưa hỗ trợ hot plug cáp mạng

. Các bạn có thể tự sửa code để thêm phần này vào.

TCP/IP stack???

Bạn chưa chỉ định thư mục chứa file header cho project thôi:Vào menu project->Build options->Projects: chọn tab dỉectories, chọn "Include search path" rồi add các thư mục có chứa file header vào (cả Header của TCPIP stack và các file header của chương trình.

Vào menu project->Build options->Projects, chỉnh như sau (thêm 2 dòng vào vào và chọn tùy chọn Asemble/... thứ hai):

Sẽ còn phát sinh 1 lỗi khác nhưng đó là do khai báo chip sử dụng của bạn thôi.

Nguyên văn bởi nttam79 Vào menu project->Build options->Projects, chỉnh như sau (thêm 2 dòng vào vào và chọn tùy chọn Asemble/... thứ hai):

Sẽ còn phát sinh 1 lỗi khác nhưng đó là do khai báo chip sử dụng của bạn thôi.híc đúng như thầy dự báo,sửa được lỗi trên thì lại xuất hiện lỗi tiếp theo.( Error [1099] Invalid MAX_TCP_SOCKETS value specified )

nhiều lỗi vậy thì làm sao mà được gọi là bản demo hay open code.microchip đúng là đánh đố

người ta quámuốn tìm 1 bản mẫu để load vào PIC xem demo thử mà khó khăn thật.

Code:void arpIpOut(unsigned char* ethFrame, unsigned long phyDstIp){

unsigned char index;struct ntEthHeader* ethHeader;struct ntIPHeader* ipHeader;ethHeader = (struct ntEthHeader*)ethFrame;ipHeader = (struct ntIPHeader*)(ethFrame + ETH_HEADER_LEN);

if(phyDstIp)index = arpSearchIP(phyDstIp);

elseindex = arpSearchIP(HTONL(ipHeader->desIPAddr));

if(index < ARP_TABLE_SIZE){

ethHeader->srcAddr = ARPMyAddr.ethAddr;ethHeader->desAddr = ARPTable[index].ethAddr;ethHeader->type = HTONS(ETH_TYPE_IP);

}else{

ethHeader->srcAddr = ARPMyAddr.ethAddr;ethHeader->desAddr.addr[0] = 0xFF;ethHeader->desAddr.addr[1] = 0xFF;ethHeader->desAddr.addr[2] = 0xFF;ethHeader->desAddr.addr[3] = 0xFF;ethHeader->desAddr.addr[4] = 0xFF;ethHeader->desAddr.addr[5] = 0xFF;ethHeader->type = HTONS(ETH_TYPE_IP);

}#ifdef ARP_DEBUGprintf("ARP Result:");ipPrintAddr(ARPTable[index].ipAddr);printf("-");ethPrintAddr(&(ethHeader->desAddr));printf("\r\n");#endif

}

Anh Tâm có hỏi" Trong hàm ArpIpOut trên, có 1 chỗ "lách luật", không tuân thủ đúng nguyên tắc làm việc của ARP, đố các bạn tìm ra, nếu có ai tìm ra mình sẽ giải thích tại sao lại làm như vậy."

Hôm nay mới đọc code của anh Tâm viết và đến đây thấy anh Tâm hỏi rất hay nên tìm hiểu và trả lời thế này, có gì anh Tâm chỉnh nha.- Trong hàm ArpIpOut dò tìm địa chỉ MAC ứng với IP trong bảng ARP cache mà thằng IP nó hỏi. Nếu trong bảng ARP cache ( bảng này <=8 trong project) không có IP tương ứng, hay bảng ARP cache chưa được cập nhật đầy đủ thì theo cách giải quyết của giao thức ARP là nó sẽ gởi đi một gới tin có tên là ARP request chứa IP mà nó cần tìm dưới dạng broadcast trong mạng LAN "Ai là người có địa chỉ IP a.b.c.d ?" để máy tính nào có địa chỉ IP trên sẽ trả lời bằng bản tin

ARP reply. - Tuy nhiên trong code của anh Tâm thì ARP không gởi gói tin ARP request mà điền luôn địa chỉ MAC đích là FF.FF.FF.FF.FF.FF vào trong gói IP, và lẽ dĩ nhiên là gói tin này sẽ gởi đến tất cả các máy trong mạng LAN nhưng thằng nào có IP đúng thì mới nhận được vì ở giao thức IP sẽ kiểm tra lại IP gói được gởi tới có phải là của mình hay không.- Như chúng ta biết bảng ARP cache được cập nhật là có 2 trường hợp. 1- là gởi bản tin ARP request, 2- mỗi khi có gói tin IP đến thì nó sẽ cập nhật IP và MAC của thằng máy tính đã gởi đi. Cứ như thế nếu thằng nào gởi thì nó sẽ tự cập nhât IP và MAC vào bảng ARP cache thôi.

Không biết Huy nói thế có đúng không nữa vì gà mà. Hehe có gì anh Tâm chỉnh lại em nhé. Thanks!

Huy tìm ra rất đúng chỗ, rptdnmqs cũng đã tìm ra nhưng chưa nói đúng nguyên nhân phải làm như vậy.

Lý do phải làm vậy là: khi giao thức IP nhận được yêu cầu gửi gói tin đi thì dữ liệu ứng dụng đã nằm sẵn trên buffer dành cho frame ethernet rồi (ethFrame). Lúc này muốn gửi đi bản tin ARP request theo "đúng luật", ta phải tạo ra một buffer mới cho bản tin ARP request sẽ gửi đi, vì nếu không sẽ ghi đè lên dữ liệu ứng dụng. Nhưng bộ nhớ RAM của ATmega 32 nhỏ quá (có 2K) nên không đủ chỗ, vì vậy buộc phải "lách luật" thôi, hehe.

Nguyên văn bởi kiemkhach10 anh tam sửa giúp em nốt cái lỗi này với.để em có thể có bản dịch demo có thể chạy đượclỗi là: :Error [1099] Invalid MAX_TCP_SOCKETS value specified.đây là project em đang sửa đến lỗi đó:

down:pic18f4620.402-fix.rarcám ơn anh nhiều!Có sửa lỗi đó thì sẽ lại có lỗi khác thôi, quan trọng là bạn phải sửa lại các define trong HardwareProfile và các file config có liên quan cho phù hợp với hardware của mình, nếu không có biên dịch thành công thì nạp vào mạch cũng không chạy.

Nếu bạn chỉ muốn biên dịch được 1 cái cho có khí thế tiếp tục thì có thể sửa các define như sau:thêm vào TCPIPconfig.h:Code:#define MAX_TCP_SOCKETS (8ul)#define TCP_TX_FIFO_SIZE (200ul)#define TCP_RX_FIFO_SIZE (200ul)#define MAX_UDP_SOCKETS (8ul)#define MAX_HTTP_CONNECTIONS (3ul)

#define MY_DEFAULT_HOST_NAME "OLIMEX BOARD"#define MY_DEFAULT_MAC_BYTE6 (0x00)#define MY_DEFAULT_IP_ADDR_BYTE4 (94ul)

Thêm vào HardwareProfile.h:

Code:#define OLIMEX_HW

Chúc thành công

Anh Tâm cho em hỏi cái này nữa: 1- Nếu mình set 2 mạch cùng MAC cùng IP thì sẽ xảy ra chuyện gì nhỉ.2- Em nạp chương trình vào mạch tới đoạn mà Ping được như hôm trước (em dùng cáp chéo cắm trực tiếp từ Board đến máy tính), lúc mới cắm điện vào mạch Ping thì OK nhưng để một lúc như mình lướt web hay làm cái gì liên quan đến internet trên máy tính thì Ping lại không được nữa mặc dù đã nhấn nút reset cứng trên mạch. Lỗi này là do tràn bộ nhớ ENC hay treo chương trình VĐK hả anh.

1- Nếu 2 mạch set cùng địa chỉ MAC và IP thì khi gửi đến địa chỉ IP này, cả 2 mạch cùng nhận được, các host khác sẽ xem như chỉ biết có 1 mạch trên LAN. Tuy nhiên khi hoạt động sẽ xảy ra lỗi: vì khi 1 máy khác truy cập theo địa chỉ đó, hai mạch sẽ cùng trả lời, điều này sẽ có thể dẫn đến giao thức TCP sẽ không thiết lập kết nối được do máy tính truy cập sẽ nhận được nhiều bản tin SYN (và có thể có số sequence number khác nhau nếu ta dùng cơ chế đánh sequence number theo thời gian). Kết quả là không truy cập webserver được.

2-Thỉnh thoảng anh cũng nhận thấy hiện tượng này và debug thử nhưng chưa tìm ra lý do. Có thể do chip ENC treo hoặc do ta quản lý bộ nhớ AVR chưa tốt. Như lúc viết giao thức ethernet anh có nói là sử dụng biến watchdog timer để reset ENC mỗi khi nó bị treo, dùng giải pháp này sẽ khắc phục được tình trạng đó, mạch chạy ổn định hơn rất nhiều. Nhưng thú thực là anh chưa tìm ra được nguồn gốc của vấn đề. Có lẽ phải dành tg tìm hiểu phần cứng ENC28J60 và debug thì mới biết được. Nếu có thời gian Huy thử nghiên cứu vấn đề này xem thử nhé.

cám ơn anh!trong phần #define có mấy cái (8ul),(200ul)... nghĩa là gì nhỉ?và ul có nghĩa là gì nhỉ?cám ơn!

ul = unsigned longcái này chỉ có ý nghĩa giúp trình biên dịch (complier) tính toán đúng khi biên dịch, không phải là khai báo kiểu biến.

p/s: bác nào cho em hỏi thêm ở con ic 28j60 ấy:+chân số 3 ( là chân CLOCK OUT) có tác dụng gì?+chân số 5 (là chân WOL) có tác dụng gì?+chân số 10(là chân reset) thì reset ở mức cao hay thấp hay khi nào rs?vậy trong 3 chân này có thể bỏ chân nào ko dùng ko?cám ơn các bác!

Bạn xem lại xem, chứ thay bình thường mà, chỉ sao cho string không vượt quá 16 ký tự thôi. Hay bạn copy thông báo lỗi lên thì mới biết được.Chân CLOCK OUT cho phép xuất xung clock từ ENC28J60 (25MHz) với các hệ số chia lập trình được (1,2,3,4,8)WOL=Wake up On LAN, 1 chức năng cho phép kích hoạt thiết bị từ trạng thái standby khi có hoạt động trên mạng LAN, nhưng lưu ý là chức năng này trên ENC28J60 chưa có nhé, chỉ chừa sẵn chân này thôi.Chân reset tích cực mức thấp.

3 chân này có thể bỏ được hết, nhưng nhớ kéo reset lên thôi.

Bài 7: Giao thức HTTP- HTTP là chữ viết tắt từ HyperText Transfer Protocol (giao thức truyền tải siêu văn bản). Nó là giao thức cơ bản mà World Wide Web sử dụng để truyền tải nội dung các trang web. HTTP xác định cách các thông điệp (các file văn bản, hình ảnh đồ hoạ, âm thanh, video, và các file multimedia khác được định dạng và truyền tải ra sao, và hoạt động của Web server và các trình duyệt Web.

- Trong mô hình của HTTP, Webserver đồng thời cũng là TCP server, mở sẵn port mặc định dành cho dịch vụ HTTP là TCP80 (ở chế độ listen), sẵn sàng đợi yêu cầu kết nối từ các client.

- Các client sẽ khởi tạo kết nối TCP thông qua port này, sau khi Webserver chấp nhận kết nối, client sẽ gửi một bản tin HTTP (HTTP message) gọi là HTTP request tới server trên kết nối TCP vừa thiết lập.

- Server sẽ trả lời lại bằng một bản tin HTTP khác là HTTP response. Bản tin này sẽ chứa nội dung trang Web yêu cầu (được viết bằng ngôn ngữ HTML).

Như vậy giao thức HTTP sẽ dựa cơ bản trên các bản tin HTTP, gồm 2 loại là HTTP request và HTTP response.

Giả sử người dùng truy cập URL: www.dientuvietnam.net/index.html (trang web có text và 10 hình ảnh jpeg):

1a-HTTP client thiết lập liên kết TCP với HTTP server (process) tại địa chỉ www.dientuvietnam.net, cổng 80 (ngầm định cho dịch vụ HTTP).

1b-HTTP server tại máy chủ www.dientuvietnam.net chờ yêu cầu kết nối TCP tại cổng 80, chấp nhận kết nối rồi thông báo với client.

2-HTTP client gửi HTTP request message (bao gồm cả URL) tới TCP connection socket.

3-HTTP server nhận request message, tạo ra HTTP response message có chứa các đối tượng được yêu cầu rồi gửi vào socket.

4-HTTP server ngắt liên kết.

5-HTTP client nhận response message có chứa file html, hiển thị html. Sau đó, phân tích file html, tìm URL của 10 hình ảnh jpeg trong tài liệu.

6-Bước 1-5 được lặp lại với từng hình ảnh.

Có nhiều phiên bản HTTP, tuy nhiên ở đây chúng ta sẽ bắt đầu với phiên bản HTTP đơn giản nhất: HTTP 1.0

1-Cấu trúc các bản tin HTTP:

Bản tin HTTP (HTTP message) gồm 2 loại: HTTP request và HTTP response. Đối với HTTP request ta có Simple-Request và Full-Request. Tương tự ta cũng có Simple-Response và Full-Response.

1.1-Bản tin HTTP request:

a-Simple-Request: có cấu trúc rất đơn giảnGET<space><Request-URI><CRLF>

b-Full-Request: có cấu trúc tổng quát gồm có 3 phần như sau[Request-Line][Headers]<CRLF>[Entity-Body]

Request-Line: có cú pháp như sau:<Method><space><Request-URI><space><HTTP-Version><CRLF>

Trong đó:- Method là tên phương thức HTTP, với HTTP 1.0 ta có các method chính là: "GET", "HEAD" và "POST".

- Request-URI là đường dẫn tới trang web cần tải, có thể là đường dẫn tương đối hay tuyệt đối.

- HTTP-Version: cho biết phiên bản HTTP đang sử dụng.

Ví dụ: - Request line với URI tuyệt đối:GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.0

- Request line với URI tương đối:GET /pub/WWW/TheProject.html HTTP/1.0

Headers: có 3 loại: General-Header; Request-Header; Entity-Header

Có nhiều Header khác nhau trong mỗi loại, ở đây không tiện trình bày hết, nhưng nhìn chung, HTTP Header có cú pháp như sau:<field-name>: <field-value> <CRLF>

Entity-Body: chứa các thông tin kèm theo request đó (ví dụ khi ta nhấn nút Submit trên 1 trang web sử dụng phương thức HTTP POST, trạng thái của các nút check, giá trị trong các ô edit text,... sẽ được chứa trong phần này.

Ví dụ: - Một bản tin HTTP request dùng phương thức GET:GET /path/file.html HTTP/1.0<CRLF>From: [email protected]<CRLF>User-Agent: HTTPTool/1.0<CRLF><CRLF>

Trong phương thức GET này, phần body là trống (không có message body)

- Một bản tin HTTP request dùng phương thức POST:POST /path/script.cgi HTTP/1.0<CRLF>From: [email protected]<CRLF>User-Agent: HTTPTool/1.0<CRLF>Content-Type: application/x-www-form-urlencoded<CRLF>Content-Length: 32<CRLF><CRLF>home=Cosby&favorite+flavor=flies

Dòng 1 là request lineDòng 2,3,4,5 là các headerDòng 6 chỉ chứa 1 ký tự xuống dòng (<CRLF>) báo hết phần Header, bắt đầu mesage bodyDòng 7 chính là phần body

Ở đây chúng ta cần lưu ý là độ dài Headers không cố định, nhưng kết thúc bằng <CRLF>. Do đó khi viết chương trình cho AVR, ta sẽ tìm chỗ nào có 2 ký tự <CRLF> liên tiếp thì đó là kết thúc Headers.

1.1-Bản tin HTTP response:

a-Simple-Response: có cấu trúc rất đơn giản, chỉ chứa phần Entity-Body[Entity-Body]

b-Full-Response: có cấu trúc tổng quát gồm có 3 phần như sau<Status-Line><Headers><CRLF>[Entity-Body]

Status-Line: có cú pháp như sau<HTTP-Version><space><Status-Code><Space><Reason-Phrase><CRLF>

Trong đó:- HTTP-Version: cho biết phiên bản giao thức HTTP. Ví dụ: "HTTP/1.0"

- Status-Code: cho biết mã trạng thái của response. Gồm 3 chữ số, được qui định như sau+ 1xx: Informational - chưa được sử dung, để dự phòng+ 2xx: Thành công (Success) - cho biết request được chấp nhận và đáp u.+ 3xx: Chuyển hướng (Redirection) - yêu cầu client chuyển hướng request+ 4xx: Lỗi do client (Client Error) - request không hợp lệ+ 5xx: Lỗi do sever (Server Error) - request hợp lệ nhưng server bị lỗi không thể trả lời

Các Status-Code cụ thể hay dùng:+ "200" ; OK+ "201" ; Created+ "202" ; Accepted+ "204" ; No Content+ "301" ; Moved Permanently+ "302" ; Moved Temporarily+ "304" ; Not Modified+ "400" ; Bad Request+ "401" ; Unauthorized+ "403" ; Forbidden+ "404" ; Not Found+ "500" ; Internal Server Error+ "501" ; Not Implemented+ "502" ; Bad Gateway+ "503" ; Service Unavailable

- Reason-Phrase: chứa nội dung text giải thích cho response.

Headers: cú pháp tương tự header của request, cũng gồm General-Header; Response-Header; Entity-Header

Entity-Body:chứa phần nội dung trả lời của response (ví du nội dung file html)

Ví dụ: Một response cho phương thức GET

HTTP/1.0 200 OK<CRLF>Date: Fri, 31 Dec 1999 23:59:59 GMT<CRLF>Content-Type: text/html<CRLF>Content-Length: 1354<CRLF><CRLF><html><body><h1>Happy New Millennium!</h1>(more file contents)...</body></html>

Dòng 1: Status-LineDòng 2: General HeaderDòng 3,4: Entity-HeaderDòng 5: <CRLF>, báo hiệu kết thúc headersDòng 6 trở đi: Entity body

Ngôn ngữ HTML và thiết kế web cho AVR webserver:Phần này không biết phải trình bày sao nữa, vì giải thích về HTML và thiết kế web thì dài dòng quá, nên chỉ hướng dẫn các bạn làm 1 trang web đơn giản để nhúng vào AVR thôi.

Đầu tiên ta dùng bất cứ 1 phần mềm có hỗ trợ thiết kế web nào cũng được, microsoft ofice word, microsoft ofice frontpage, ofice publisher, ... (mình dùng Adobe Dreamweaver CS5 do sẵn tiện đang dùng để viết PHP) để thiết kế 1 trang web đơn giản tương tự như sau:

Bạn nào chưa biết làm hoặc lười có thể làm như sau:1-Mở notepad.

2-Copy code html sau vào:Code:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>AVR web server</title></head><body style="background: #048C04; color: #FFFFFF"><br><table bgcolor="#489D48" border="0" width="975" cellpadding="0" cellspacing="0" align="center"> <tbody> <tr> <td height="31" align="center"> <div style="font-size:48pt; color:#F4FC8F"><strong>REMOTE CONTROL AVR WEBSERVER</strong></div> </td> </tr> </tbody></table><table width="100%" cellpadding="0" cellspacing="0">

<tbody> <tr> <td> <br> <div align="center"> <div style="background: #ffffff; color: #000000; width:975px; text-align:left"> <div style="padding:0px 15px 0px 15px" align="left"> <br> <table style="background: #999999; color: #000000; border: 1px solid #6581c1" cellpadding="6" cellspacing="1" border="0" width="100%" align="center"> <tbody> <tr style="background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF"> <td width="100%"> <div style="font-size:10pt"><strong>DESIGNER: THANH - TAM</strong></div> </td> <td nowrap="nowrap" style="padding:0px"> </td> </tr> </tbody> </table> <br> <table style="background: #999999; color: #000000; border: 1px solid #6581c1" cellpadding="6" cellspacing="1" border="0" width="100%" align="center"> <thead> <tr valign="top"> <td style="background: #FFFFFF; color: #048C04" colspan="3"><strong>Remote control webserver with ATmega32</a></strong></td> </tr> </thead> <tbody id="collapseobj_vietvbb_stats" style=""> <tr> <td style="background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF"> <table border="1" cellpadding="0" cellspacing="0" width="100%"> <tbody> <tr style="font-size:12pt"> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div> <strong>Camera view</strong> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div> <strong>Devices control</strong> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="50%"> <div> <strong>Sensors</strong> </div> </td>

</tr> <tr style="font:bold 12pt"> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div id="vietvbb_topstats_s_content" style="display: block;"> <img src="http://d.f5.photo.zdn.vn/upload/original/2011/06/19/1/24/1308421475585659804_574_0.jpg" width="320" height="240" /> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div> <form method="POST" action=""> <p align="left"><input type="checkbox" name="RELAY1" value="ON"%RL1>Relay 01</p> <p align="left"><input type="checkbox" name="RELAY2" value="ON"%RL2>Relay 02</p> <p align="left"><input type="checkbox" name="RELAY3" value="ON"%RL3>Relay 03</p> <p align="left"><input type="checkbox" name="RELAY4" value="ON"%RL4>Relay 04</p> <p align="left"><input type="submit" value="Submit" name="CTRL"></p> </form> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="50%"> <div> <table border="1" width="100%" id="table1"> <tr> <td>Environment Sensors</td> <td width="42">Value</td> <td width="33">Unit</td> <td width="150">&nbsp;</td> </tr> <tr> <td>Sensor 01</td> <td width="42">%AD0</td> <td width="33"><sup>o</sup>C</td> <td width="150">&nbsp;</td> </tr> <tr> <td>Sensor 02</td> <td width="42">%AD1</td> <td width="33"><sup>o</sup>C</td> <td width="150">&nbsp;</td> </tr> </table> </div> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table>

<br><br><br> <div align="center">Timezone: GMT+7. Current time <span>%TI</span>.</div> <br> </div> </div> </div> <form action="footer" method="get"> <br> <div align="center"> <div align="center">Designed by thanh - tam</div> </div> </form> </td> </tr> </tbody> </table> </div></body></html>

3-Chọn Save as: Chọn Save as type: All file; Encoding: UTF-8; Đặt tên file là web.html (nhớ đúng đuôi nhé).

4-Dùng trình duyệt mở xem thử.

5-Dùng Microsoft Office word hay bất cứ phần mềm soạn thảo web nào edit lại theo ý mình.

Đoạn code trên chính là mã HTML mô tả nội dung trang web mà webserver phải trả lời lại cho 1 bản tin HTTP request (GET hay POST)

Trong trang web trên ta có 1 hình ảnh (chú cún nhà mình) nhưng ta không lưu hình ảnh này trên ROM mà upload trên 1 server khác rồi nhúng code vào thôi, để tiết kiệm bộ nhớ cho AVR.

Như vậy kịch bản truy cập vào webserver với trang web trên như sau (giả sử ta chỉ mới truy cập trong mạng LAN với IP của mạch là 192.168.1.10):

1-Webserver (mạch AVR+ENC28J60) mở sẵn cổng TCP 80 (listen) đợi nhận kết nối từ client.

2-Ta mở trình duyệt, gõ địa chỉ http://192.168.1.10

3-Máy tính khởi tạo 1 kết nối TCP tới địa chỉ IP 192.168.1.10, port đích là 80, port nguồn do nó tự chọn (gửi bản tin SYN).

4-Webserver chấp nhận kết nối (trả lời bản tin SYN), client xác nhận ACK (xem lại phần giao thức TCP). Kết nối TCP được thiết lập.

5-Client gửi bản tin HTTP request với phương thức GET. Nội dung như sau (ví dụ):Code:GET http://192.168.1.10/index.html HTTP/1.0<CRLF>

User-Agent: Chrome/15.0<CRLF><CRLF>

6-Webserver trả lời bằng một bản tin HTTP response, với phần Entity-body chính là nội dung mã HTML của trang web trên:Code:HTTP/1.0 200 Document follows<CRLF>Server: AVR_Small_Webserver<CRLF>Content-Type: text/html<CRLF>Content-Length: 1234<CRLF><CRLF><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>AVR web server</title></head><body style="background: #048C04; color: #FFFFFF"><br><table bgcolor="#489D48" border="0" width="975" cellpadding="0" cellspacing="0" align="center"> <tbody> <tr> <td height="31" align="center"> <div style="font-size:48pt; color:#F4FC8F"><strong>REMOTE CONTROL AVR WEBSERVER</strong></div> </td> </tr> </tbody></table><table width="100%" cellpadding="0" cellspacing="0"> <tbody> <tr> <td> <br> <div align="center"> <div style="background: #ffffff; color: #000000; width:975px; text-align:left"> <div style="padding:0px 15px 0px 15px" align="left"> <br> <table style="background: #999999; color: #000000; border: 1px solid #6581c1" cellpadding="6" cellspacing="1" border="0" width="100%" align="center"> <tbody> <tr style="background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF"> <td width="100%"> <div style="font-size:10pt"><strong>DESIGNER: THANH - TAM</strong></div> </td> <td nowrap="nowrap" style="padding:0px"> </td> </tr> </tbody> </table>

<br> <table style="background: #999999; color: #000000; border: 1px solid #6581c1" cellpadding="6" cellspacing="1" border="0" width="100%" align="center"> <thead> <tr valign="top"> <td style="background: #FFFFFF; color: #048C04" colspan="3"><strong>Remote control webserver with ATmega32</a></strong></td> </tr> </thead> <tbody id="collapseobj_vietvbb_stats" style=""> <tr> <td style="background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF"> <table border="1" cellpadding="0" cellspacing="0" width="100%"> <tbody> <tr style="font-size:12pt"> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div> <strong>Camera view</strong> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div> <strong>Devices control</strong> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="50%"> <div> <strong>Sensors</strong> </div> </td> </tr> <tr style="font:bold 12pt"> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div id="vietvbb_topstats_s_content" style="display: block;"> <img src="http://d.f5.photo.zdn.vn/upload/original/2011/06/19/1/24/1308421475585659804_574_0.jpg" width="320" height="240" /> </div> </td> <td style="padding: 5px 5px 5px 5px;" width="25%"> <div> <form method="POST" action=""> <p align="left"><input type="checkbox" name="RELAY1" value="ON"%RL1>Relay 01</p> <p align="left"><input type="checkbox" name="RELAY2" value="ON"%RL2>Relay 02</p> <p align="left"><input type="checkbox" name="RELAY3" value="ON"%RL3>Relay 03</p> <p align="left"><input type="checkbox" name="RELAY4" value="ON"%RL4>Relay 04</p> <p align="left"><input type="submit" value="Submit" name="CTRL"></p> </form> </div> </td>

<td style="padding: 5px 5px 5px 5px;" width="50%"> <div> <table border="1" width="100%" id="table1"> <tr> <td>Environment Sensors</td> <td width="42">Value</td> <td width="33">Unit</td> <td width="150">&nbsp;</td> </tr> <tr> <td>Sensor 01</td> <td width="42">%AD0</td> <td width="33"><sup>o</sup>C</td> <td width="150">&nbsp;</td> </tr> <tr> <td>Sensor 02</td> <td width="42">%AD1</td> <td width="33"><sup>o</sup>C</td> <td width="150">&nbsp;</td> </tr> </table> </div> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <br><br><br> <div align="center">Timezone: GMT+7. Current time <span>%TI</span>.</div> <br> </div> </div> </div> <form action="footer" method="get"> <br> <div align="center"> <div align="center">Designed by thanh - tam</div> </div> </form> </td> </tr> </tbody> </table> </div></body></html>

7-Webserver đóng kết nối TCP.

8-Trình duyệt trên client phân tích mã HTML, hiển thị nôi dung trang web, phần hình ảnh (<img

src="http://d.f5.photo.zdn.vn/upload/original/2011/06/19/1/24/1308421475585659804_574_0.jpg" width="320" height="240" />) vẫn chưa có (chừa trống).

9-Client căn cứ vào URL của hình ảnh (http://d.f5.photo.zdn.vn/upload/original/2011/06/19/1/24/1308421475585659804_574_0.jpg) thiết lập 1 kết nối TCP mới, tải hình ảnh trên về và hiển thị lên.

Bây giờ bắt đầu phần Coding:

Tương tự, ta tạo ra các file source và header trong project:File "http.c"Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include "packet.h"#include "ethernet.h"#include "http.h"#include "webpage.h"#include "uart.h"#include "ntAVRnet.h"

File "http.h"Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef HTTP_H#define HTTP_H

//----------------------------------------------------------------------------#endif //HTTP_H

Ta tạo thêm 1 file header có tên "webpage.h" chứa nội dung trang webCode://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef WEBPAGE_H#define WEBPAGE_H

//----------------------------------------------------------------------------#endif //WEBPAGE_H

Bây giờ ta bắt đầu với file "webpage.h" trước, chuyển nội dung file HTML mà ta đã soạn thảo ở phần trước vào trong ROM của AVR:

Ta khai báo trong "webpage.h" một chuỗi ký tự lưu trong bộ nhớ ROM:Code://----------------------------------------------------------------------------

// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef WEBPAGE_H#define WEBPAGE_H//#include <avr/pgmspace.h>

PROGMEM char Page1[] = {};//----------------------------------------------------------------------------#endif //WEBPAGE_H

Tiếp theo ta chuyển nội dung file html trên vào chuỗi trên. Làm theo các bước sau:

1-Mở notepad, copy tất cả mã html trong file "web.html" đã chuẩn bị vào. Có thể bỏ các ký tự <space> ở đầu dòng cho tiết kiệm bộ nhớ.

2-Bấm Ctrl-H để mở công cụ find & replace: tìm ký tự dấu nháy kép: " và thay tất cả bằng: \". Lý do của thao tác này là trong ngôn ngữ C, dấu nháy kép sẽ kết thúc 1 chuỗi, do đó nếu trong chuỗi của ta có dấu nháy này thì phải thay bằng \" .

Kết quả tại đây ta sẽ được nội dung trên notepad như sau:Code:<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\" dir=\"ltr\" lang=\"en\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>AVR web server</title></head><body style=\"background: #048C04; color: #FFFFFF\"><br><table bgcolor=\"#489D48\" border=\"0\" width=\"975\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\"><tbody><tr><td height=\"31\" align=\"center\"><div style=\"font-size:48pt; color:#F4FC8F\"><strong>REMOTE CONTROL AVR WEBSERVER</strong></div></td></tr></tbody></table><table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"><tbody><tr><td><br><div align=\"center\"><div style=\"background: #ffffff; color: #000000; width:975px; text-align:left\"><div style=\"padding:0px 15px 0px 15px\" align=\"left\"><br><table style=\"background: #999999; color: #000000; border: 1px solid #6581c1\" cellpadding=\"6\" cellspacing=\"1\" border=\"0\" width=\"100%\" align=\"center\">

<tbody><tr style=\"background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF\"><td width=\"100%\"><div style=\"font-size:10pt\"><strong>DESIGNER: THANH - TAM</strong></div></td><td nowrap=\"nowrap\" style=\"padding:0px\"></td></tr></tbody></table><br><table style=\"background: #999999; color: #000000; border: 1px solid #6581c1\" cellpadding=\"6\" cellspacing=\"1\" border=\"0\" width=\"100%\" align=\"center\"><thead><tr valign=\"top\"><td style=\"background: #FFFFFF; color: #048C04\" colspan=\"3\"><strong>Remote control webserver with ATmega32</a></strong></td></tr></thead><tbody id=\"collapseobj_vietvbb_stats\" style=\"\"><tr><td style=\"background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF\"><table border=\"1\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\"><tbody><tr style=\"font-size:12pt\"> <td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\"><div><strong>Camera view</strong></div></td><td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\"><div><strong>Devices control</strong></div></td><td style=\"padding: 5px 5px 5px 5px;\" width=\"50%\"><div><strong>Sensors</strong></div></td></tr><tr style=\"font:bold 12pt\">

<td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\">

<div id=\"vietvbb_topstats_s_content\" style=\"display: block;\">

<img src=\"http://d.f5.photo.zdn.vn/upload/original/2011/06/19/1/24/1308421475585659804_574_0.jpg\" width=\"320\" height=\"240\" /></div></td><td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\"><div>

<form method=\"POST\" action=\"\"><p align=\"left\"><input type=\"checkbox\" name=\"RELAY1\" value=\"ON\"%RL1>Relay 01</p><p align=\"left\"><input type=\"checkbox\" name=\"RELAY2\" value=\"ON\"%RL2>Relay 02</p><p align=\"left\"><input type=\"checkbox\" name=\"RELAY3\" value=\"ON\"%RL3>Relay 03</p><p align=\"left\"><input type=\"checkbox\" name=\"RELAY4\" value=\"ON\"%RL4>Relay 04</p><p align=\"left\"><input type=\"submit\" value=\"Submit\" name=\"CTRL\"></p></form></div></td><td style=\"padding: 5px 5px 5px 5px;\" width=\"50%\"><div><table border=\"1\" width=\"100%\" id=\"table1\"><tr><td>Environment Sensors</td><td width=\"42\">Value</td><td width=\"33\">Unit</td><td width=\"150\">&nbsp;</td></tr><tr><td>Sensor 01</td><td width=\"42\">%AD0</td><td width=\"33\"><sup>o</sup>C</td><td width=\"150\">&nbsp;</td></tr><tr><td>Sensor 02</td><td width=\"42\">%AD1</td><td width=\"33\"><sup>o</sup>C</td><td width=\"150\">&nbsp;</td></tr></table></div></td></tr></tbody></table></td></tr></tbody></table><br><br><br><div align=\"center\">Timezone: GMT+7. Current time <span>%TI</span>.</div><br></div></div></div><form action=\"footer\" method=\"get\"><br><div align=\"center\"><div align=\"center\">Designed by thanh - tam</div></div></form></td>

</tr></tbody></table></div></body></html>

3-Ta thêm dấu nháy kép vào đầu và cuối mỗi dòng.

4-Copy tất cả, bỏ vào giữa 2 dấu ngoặc nhọn trong khai báo PROGMEM char Page1[] = {};

Kết quả ta sẽ được file "webpage.h" như vầy:Code://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#ifndef WEBPAGE_H#define WEBPAGE_H//#include <avr/pgmspace.h>

PROGMEM char Page1[] = {"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">""<html xmlns=\"http://www.w3.org/1999/xhtml\" dir=\"ltr\" lang=\"en\">""<head>""<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">""<title>AVR web server</title>""</head>""<body style=\"background: #048C04; color: #FFFFFF\">""<br>""<table bgcolor=\"#489D48\" border=\"0\" width=\"975\" cellpadding=\"0\" cellspacing=\"0\" align=\"center\">""<tbody>""<tr>""<td height=\"31\" align=\"center\">""<div style=\"font-size:48pt; color:#F4FC8F\"><strong>REMOTE CONTROL AVR WEBSERVER</strong></div>""</td>""</tr>""</tbody>""</table>""<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">""<tbody>""<tr>""<td>""<br>""<div align=\"center\">""<div style=\"background: #ffffff; color: #000000; width:975px; text-align:left\">""<div style=\"padding:0px 15px 0px 15px\" align=\"left\">""<br>""<table style=\"background: #999999; color: #000000; border: 1px solid #6581c1\" cellpadding=\"6\" cellspacing=\"1\" border=\"0\" width=\"100%\" align=\"center\">""<tbody>"

"<tr style=\"background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF\">""<td width=\"100%\">""<div style=\"font-size:10pt\"><strong>DESIGNER: THANH - TAM</strong></div>""</td>""<td nowrap=\"nowrap\" style=\"padding:0px\">""</td>""</tr>""</tbody>""</table>""<br>""<table style=\"background: #999999; color: #000000; border: 1px solid #6581c1\" cellpadding=\"6\" cellspacing=\"1\" border=\"0\" width=\"100%\" align=\"center\">""<thead>""<tr valign=\"top\">""<td style=\"background: #FFFFFF; color: #048C04\" colspan=\"3\"><strong>Remote control webserver with ATmega32</a></strong></td>""</tr>""</thead>""<tbody id=\"collapseobj_vietvbb_stats\" style=\"\">""<tr>""<td style=\"background: #FFFFFF; color: #545454; border-left: 1px solid #FFFFFF; border-top: 1px solid #FFFFFF\">""<table border=\"1\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">""<tbody>""<tr style=\"font-size:12pt\">""<td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\">""<div>""<strong>Camera view</strong>""</div>""</td>""<td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\">""<div>""<strong>Devices control</strong>""</div>""</td>""<td style=\"padding: 5px 5px 5px 5px;\" width=\"50%\">""<div>""<strong>Sensors</strong>""</div>""</td>""</tr>""<tr style=\"font:bold 12pt\">"

"<td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\">"

"<div id=\"vietvbb_topstats_s_content\" style=\"display: block;\">"

"<img src=\"http://d.f5.photo.zdn.vn/upload/original/2011/06/19/1/24/1308421475585659804_574_0.jpg\" width=\"320\" height=\"240\" />""</div>""</td>""<td style=\"padding: 5px 5px 5px 5px;\" width=\"25%\">""<div>"

"<form method=\"POST\" action=\"\">""<p align=\"left\"><input type=\"checkbox\" name=\"RELAY1\" value=\"ON\"%RL1>Relay 01</p>""<p align=\"left\"><input type=\"checkbox\" name=\"RELAY2\" value=\"ON\"%RL2>Relay 02</p>""<p align=\"left\"><input type=\"checkbox\" name=\"RELAY3\" value=\"ON\"%RL3>Relay 03</p>""<p align=\"left\"><input type=\"checkbox\" name=\"RELAY4\" value=\"ON\"%RL4>Relay 04</p>""<p align=\"left\"><input type=\"submit\" value=\"Submit\" name=\"CTRL\"></p>""</form>""</div>""</td>""<td style=\"padding: 5px 5px 5px 5px;\" width=\"50%\">""<div>""<table border=\"1\" width=\"100%\" id=\"table1\">""<tr>""<td>Environment Sensors</td>""<td width=\"42\">Value</td>""<td width=\"33\">Unit</td>""<td width=\"150\">&nbsp;</td>""</tr>""<tr>""<td>Sensor 01</td>""<td width=\"42\">%AD0</td>""<td width=\"33\"><sup>o</sup>C</td>""<td width=\"150\">&nbsp;</td>""</tr>""<tr>""<td>Sensor 02</td>""<td width=\"42\">%AD1</td>""<td width=\"33\"><sup>o</sup>C</td>""<td width=\"150\">&nbsp;</td>""</tr>""</table>""</div>""</td>""</tr>""</tbody>""</table>""</td>""</tr>""</tbody>""</table>""<br><br><br>""<div align=\"center\">Timezone: GMT+7. Current time <span>%TI</span>.</div>""<br>""</div>""</div>""</div>""<form action=\"footer\" method=\"get\">""<br>""<div align=\"center\">""<div align=\"center\">Designed by thanh - tam</div>""</div>""</form>""</td>"

"</tr>""</tbody>""</table>""</div>""</body>""</html>"};//----------------------------------------------------------------------------#endif //WEBPAGE_H

Ai mà lười thì copy y chang file này, kết quả là trang web sẽ có hình chú cún nhà mình.

Tiếp theo, ta quay lại file "http.h". Thêm các define cho các method của request:Code://#define HTTP_REQUEST_GET 1//"GET"#define HTTP_REQUEST_POST 2//"POST"#define HTTP_REQUEST_HEAD 3//"HEAD"#define HTTP_REQUEST_UNKNOWN 4//Request quai qui gi do ma ta khong biet//#define HTTP_REQUEST_GET_STR "GET"#define HTTP_REQUEST_POST_STR "POST"#define HTTP_REQUEST_HEAD_STR "HEAD"//

Sau đó là khai báo struct tương ứng với bản tin request:Code://struct httpRequest{

unsigned char method; //end with spaceunsigned char *requestURI; //end with spaceunsigned char *version; //end with \n\runsigned char *header; //end with \n\r\n\runsigned char *body; //unsigned int bodyLen;

};

Ở đây các bạn lưu ý là khác với các header khác, ta dùng chủ yếu biến con trỏ để trỏ các field trong bản tin request, vì kích thước các field trong bản tin không cố định, nên vị trí bắt đầu field cũng không cố định. Ta dùng biến co trỏ để trỏ đến đầu mỗi field, còn kết thúc field thì đương nhiên là trước khi bắt đầu filed tiếp theo, trừ field cuối cùng là body, ta cho nó 1 biến chiều dài.

Webserver phải có khả năng thiết lập đồng thời nhiều phiên kết nối, do đó ta khai báo 1 struct nữa lưu thông tin về mỗi phiên kết nối HTTP:Code:struct httpSession{

unsigned char status; //Trang thai hien taiconst prog_char *headerPointer; //Con tro den header trong bo

nho chuong trinhconst prog_char *bodyPointer; //Con tro den du lieu can gui trong bo

nho chuong trinhunsigned char *authStr; //Con tro den chuoi xac

thucunsigned char auth; //Xac thucstruct httpRequest rqst; //Request tuong ung

struct tcpSession *pTCPSession;};

Trong struct trên ta có dùng từ khóa prog_char (để chỉ bộ nhớ chương trình: ROM) nên các bạn nhớ include file "pgmspace.h" vào (đầu file) nếu không sẽ có báo lỗi:Code:#include <avr/pgmspace.h>

Cuối cùng là define các trạng thái của 1 phiên HTTP và số phiên tối đa có thể thiết lập đồng thời:Code://#define HTTP_STATUS_IDLE 0#define HTTP_STATUS_GOT_REQUEST 1#define HTTP_STATUS_HEADER_SENT 2#define HTTP_STATUS_HEADER_RECEIVED 3#define HTTP_STATUS_FINISH 4//#define MAX_HTTP_SESSION 8//

Tới lượt file source "http.c"

Đầu tiên ta khai báo một số bản tin response chuẩn để webserver sử dụng:Bản tin response cho một request GET/POST hợp lệ, kèm theo sẽ là nội dung trang web:Code:prog_char http_pageheader_ok[]={ "HTTP/1.0 200 Document follows\r\n"

"Server: AVR_Small_Webserver\r\n"

"Content-Type: text/html\r\n\r\n"};

Trên đây là Status-line và Response Header, Entity Header của bản tin response, phần Entity-body chính là nội dung webpage mà ta đã chuẩn bị trong file "webpage.h"

Bản tin response yêu cầu xác thực. Tất nhiên trang web remote control của chúng ta phải có xác thực username/password rồi, chắc các bạn không muốn ai cũng có thể truy cập vào bật tắt thiết bị lung tung chứ.Code:prog_char http_pageheader_unauth[]={ "HTTP/1.0 401 Unauthorized\r\n"

"Server: AVR_Small_Webserver\r\n"

"WWW-Authenticate: Basic realm=\"NeedPassword\""

"\r\nContent-Type: text/html\r\n\r\n"};

Bản tin báo việc xác thực thất bại:Code:prog_char http_pageheader_auth_error[] = {"401 Unauthorized%END"};

Bản tin báo lỗi request một đối tượng không có:Code:prog_char http_error_notimp[]={ "HTTP/1.1 501 Not Implemented\r\n"

"Server: AVR_Small_Webserver\r\n"

"WWW-Authenticate: Basic realm=\"My AVR web server\""

"\r\nContent-Type: text/html\r\n\r\n"};

Khai báo 1 array các biến struct chứa thông tin về các phiên HTTP:Code:struct httpSession httpSessionTable[MAX_HTTP_SESSION];

Khai báo 1 char array chứa chuỗi xác thực:Code:unsigned char http_auth_password[20];

Đầu tiên, ta viết hàm tính mã xác thực cho username/password:Code://----------------------------------------------------------------------------PROGMEM char BASE64CODE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";//void decode_base64 (unsigned char *str1_in,unsigned char *str2_out){

unsigned char *strp; unsigned char end_byte = 0;

strp = str2_out;

while (*str1_in != 0){

*str2_out++= (*str1_in & 0xFC)>>2;

*str2_out = (((*str1_in++)&0x03)<<4); if (*str1_in==0){

str2_out++; end_byte = 2;

break;}else{

*str2_out++ += ((*str1_in & 0xF0)>>4);*str2_out = (((*str1_in++)& 0x0F)<<2);if (*str1_in==0){

str2_out++; end_byte = 1; break;

}else{

*str2_out++ += ((*str1_in & 0xC0)>>6);*str2_out++= *str1_in++ & 0x3F;

}}

}

*str2_out = 0;

while(strp != str2_out){

*strp = pgm_read_byte(&BASE64CODE[(*strp) & 0x3F]);strp++;

} while (end_byte--) { *strp = '='; strp++; } *strp = 0; }

Hàm khởi tạo giao thức HTTP (thực chất là khởi tạo giá trị ban đầu cho các biến):Code://----------------------------------------------------------------------------void httpInit(){

unsigned char i;decode_base64((unsigned char*)HTTP_AUTH_STRING,http_auth_password);for(i=0; i < MAX_HTTP_SESSION; i++){

httpSessionTable[i].status = HTTP_STATUS_IDLE;}//UpdateCtrlSts(0x00);

}

Ta mở file "ntAVRnet.h" khai báo chuỗi xác thực username/password cho trang web của chúng ta (giả sử username là admin, password là 1234):Code:#define HTTP_AUTH_STRING "admin:1234"

Tiếp theo, ta viết hàm xử lý 1 request nhận được. Lúc này, request nhận được qua port TCP 80 đang nằm trên buffer, ngay sau TCP Header. Nhiệm vụ của hàm này là xác định phương thức (method) của request, xác định vị trí các field, và lưu nhưng thông tin này vào struct httpRequest tương ứng.Code://----------------------------------------------------------------------------//Ham xu ly mot request cua giao thuc HTTPvoid httpGetRequest(unsigned char *buffer,unsigned int bufferLen,struct httpRequest* rqst){

unsigned int i;if((buffer[0] == 'G')&&(buffer[1] == 'E')&&(buffer[2] == 'T')){

rqst->method = HTTP_REQUEST_GET;#ifdef HTTP_DEBUGprintf("GET\r\n");#endif

}else if((buffer[0] == 'P')&&(buffer[1] == 'O')&&(buffer[2] == 'S')&&(buffer[3] == 'T')){

rqst->method = HTTP_REQUEST_POST;#ifdef HTTP_DEBUGprintf("POST\r\n");#endif

}else if((buffer[0] == 'H')&&(buffer[1] == 'E')&&(buffer[2] == 'A')&&(buffer[3] == 'D')){

rqst->method = HTTP_REQUEST_HEAD;#ifdef HTTP_DEBUGprintf("HEAD\r\n");#endif

}else{rqst->method = HTTP_REQUEST_UNKNOWN;#ifdef HTTP_DEBUGprintf("UNKNOWN\r\n");#endif

}//Bat dau tim URI va HTTP versioni = 0;while(++i<bufferLen){

if(buffer[i] == ' '){ //khoang trang dau tienrqst->requestURI = &buffer[i+1];break;

}}while(++i<bufferLen){

if(buffer[i] == ' '){ //khoang trang dau tienrqst->version = &buffer[i+1];break;

}}//Tim headerwhile(++i<bufferLen){

if((buffer[i] == 0x0d) && (buffer[i+1] == 0x0a)){ //\n\r dau tien, bat dau header

rqst->header = &buffer[i+2];break;

}}//Tim ket thuc headerwhile(++i<bufferLen){

if((buffer[i] == 0x0d) && (buffer[i+1] == 0x0a) && (buffer[i+2] == 0x0d)){ //\n\r\n\r

i += 4;break;

}}rqst->body = &buffer[i];rqst->bodyLen = bufferLen - i;

#ifdef HTTP_DEBUGprintf("Buffer Len:%d\r\n",bufferLen);printf("URI start:%d\r\n",(rqst->requestURI - buffer));printf("HTTP Version:%d\r\n",(rqst->version - buffer));printf("Header:%d\n\r",(rqst->header - buffer));printf("Body:%d\n\r",(unsigned int)(rqst->body - buffer));printf("Body:%x-%x\n\r",(unsigned int)(rqst->body),(unsigned

int)buffer);printf("Body i:%d\n\r",i);#endif

}

Và hàm lấy giá trị của 1 trường HTTP Header:Code://----------------------------------------------------------------------------//Ham lay gia tri mot truong trong HTTP Header,// tra lai con tro vi tri bat dau value cua truong nayunsigned char * httpHeaderGetField(const prog_char fieldname[],struct httpRequest *rqst){

unsigned char *header;unsigned int i,j,headerLen;header = rqst->header;headerLen = (rqst->header) - (rqst->body) - 4;i=0;j=0;while(i<headerLen){

if(header[i++] != pgm_read_byte(fieldname + j++)){j = 0;

}if(pgm_read_byte(fieldname + j) == 0){

return(header+i+2);}

}return(0);

}

Last edited by nttam79; 23-11-11 at 11:20.

Chia sẻ o

o

o

o

o

| Cảm ơn

tienhuypro, robocon2011 và hieppro89 đã cảm ơn nội dung này.Trả lời   Reply With Quote         23-11-11 11:57 #163 nttam79

Thành viên chính thức Tham gia

Feb 2009Bài viết

91Tiếp theo, ta viết 2 hàm chính của giao thức HTTP: hàm gửi một bản tin HTTP đi và hàm xử lý 1 request nhận được:

Đầu tiên, ta cần 2 hàm tìm kiếm chuỗi mà ta sẽ sử dụng trong 2 hàm chính trên:

- Hàm tìm 1 chuỗi lưu trong bộ nhớ chương trình (ROM) trong 1 chuỗi lưu trong RAMCode://----------------------------------------------------------------------------unsigned int findstr(const prog_char progstr[],unsigned char* str,unsigned int len){

unsigned int i,j;i=0;j=0;while(i<len){

if(str[i++] != pgm_read_byte(progstr + j++)){j = 0;

}if(pgm_read_byte(progstr + j) == 0){

return(i-j);}

}return(-1);

}

- Hàm tìm 1 chuỗi lưu trong bộ nhớ RAM trong 1 chuỗi cũng lưu trong RAMCode://----------------------------------------------------------------------------unsigned int findstrdatamem(unsigned char* str1,unsigned char* str2,unsigned int len){

unsigned int i,j;i=0;j=0;while(i<len){

if(str2[i++] != str1[j++]){j = 0;

}if(str1[j] == 0){

return(i-j);}if(str2[i] == 0){

return(-1);}

}return(-1);

}

Và đây là hàm gửi đi một bản tin HTTP:Code://----------------------------------------------------------------------------//Ham gui mot doan du lieu chua trong bo nho chuong trinh ra theo giao thuc HTTPvoid HTTPSend(const prog_char progdata[],unsigned int dataLen,struct tcpSession *pSession, unsigned char endData)

{unsigned int i;unsigned int srcDataIdx = 0;unsigned char* dataBuffer;unsigned char Flags;unsigned char tmpChr;

Flags = TCP_PSH_FLAG;dataBuffer = ethGetBuffer() + ETH_HEADER_LEN + IP_HEADER_LEN +

TCP_HEADER_LEN;if(dataLen == 0){

Flags |= TCP_ACK_FLAG;if(endData){

if(srcDataIdx == dataLen){Flags |= TCP_FIN_FLAG;pSession->sesState = TCP_STATE_FIN_WAIT1;

}}TCPPackedSend(pSession,Flags,0,dataBuffer);

}while(srcDataIdx < dataLen){

i = 0;while(i<MAX_SEGMENT_SIZE){

tmpChr = pgm_read_byte(progdata + srcDataIdx++);dataBuffer[i++] = tmpChr; //Copy data to tcp data

bufferif(srcDataIdx==dataLen){

break;}

}Flags |= TCP_ACK_FLAG;if(endData){

if(srcDataIdx == dataLen){Flags |= TCP_FIN_FLAG;pSession->sesState = TCP_STATE_FIN_WAIT1;

}}TCPPackedSend(pSession,Flags,i,dataBuffer);//delay_ms(100);#ifdef NETSTACK_DEBUGprintf("Sent %d byte\r\n",srcDataIdx);#endif

}}

Hàm xử lý 1 request nhận được:Code://----------------------------------------------------------------------------//Ham xu ly mot goi thuoc giao thuc HTP nhan duocvoid httpDataIn(unsigned char *buffer,unsigned int bufferLen,struct tcpSession *pSession){

unsigned char i;unsigned char *tmpstr;prog_char Auth_str[]="Authorization";//Tim xem cophien HTTP service da co cho phien TCP nay khongfor(i=0; i < MAX_HTTP_SESSION; i++){

if((httpSessionTable[i].status != HTTP_STATUS_IDLE) && (httpSessionTable[i].pTCPSession == pSession))

break;}if(i == MAX_HTTP_SESSION){

//Tim 1 phien trongfor(i=0; i < MAX_HTTP_SESSION; i++){

if(httpSessionTable[i].status == HTTP_STATUS_IDLE){httpSessionTable[i].pTCPSession = pSession;break;

}}

}if(httpSessionTable[i].status == HTTP_STATUS_IDLE){//Neu day la 1 HTTP request moi

//Kiem tra method cua requesthttpGetRequest(buffer,bufferLen,&(httpSessionTable[i].rqst));if(httpSessionTable[i].rqst.method == HTTP_REQUEST_UNKNOWN){//Neu request khong biet, thoat tro ve trang thai idle

httpSessionTable[i].status = HTTP_STATUS_IDLE;return;

}else{//Set trang thai cua HTTP session thanh HEADER_RECEIVED (da

nhan header)httpSessionTable[i].status =

HTTP_STATUS_HEADER_RECEIVED;}

}//Neu da nhan headerif(httpSessionTable[i].status == HTTP_STATUS_HEADER_RECEIVED){

//Kiem tra xem da xac thuc hay chuaif(httpSessionTable[i].auth != 1){

tmpstr = httpHeaderGetField(Auth_str,&(httpSessionTable[i].rqst));

if(findstrdatamem(http_auth_password,tmpstr,httpSessionTable[i].rqst.body-tmpstr) != -1){

#ifdef HTTP_DEBUGprintf("Auth OK\n\r");printfStr(http_auth_password);printf("Input\n\r");printfStr(tmpstr);#endifhttpSessionTable[i].auth = 1;

//Neu xac thuc khong hop le}else{

#ifdef HTTP_DEBUGprintf("Auth fail\n\r");printf("Auth string:");printfStr(http_auth_password);printf("\n\r");printf("Received string:");printfStrLen(tmpstr,0,20);#endif

HTTPSend(http_pageheader_unauth,sizeof(http_pageheader_unauth)-1,pSession,0);

HTTPSend(http_pageheader_auth_error,sizeof(http_pageheader_auth_error)-1,pSession,1);

httpSessionTable[i].status = HTTP_STATUS_IDLE;return;

}}//Process for each method//If GET methodif(httpSessionTable[i].rqst.method == HTTP_REQUEST_GET){

#ifdef HTTP_DEBUGprintf("GET HEADER:\n\r");printfStrLen(httpSessionTable[i].rqst.header,0

,(httpSessionTable[i].rqst.body - httpSessionTable[i].rqst.header) + httpSessionTable[i].rqst.bodyLen);

#endif//If GET the main websiteif((httpSessionTable[i].rqst.requestURI[0] == '/') &&

(httpSessionTable[i].rqst.requestURI[1] == ' ')){

HTTPSend(http_pageheader_ok,sizeof(http_pageheader_ok)-1,pSession,0);HTTPSend(Page1,sizeof(Page1)-1,pSession,1);httpSessionTable[i].status = HTTP_STATUS_IDLE;

//If error}else{

HTTPSend(http_error_notimp,sizeof(http_error_notimp),pSession,1);httpSessionTable[i].status = HTTP_STATUS_IDLE;

}return;

//If POST method}else if(httpSessionTable[i].rqst.method == HTTP_REQUEST_POST)

{//Update POST dataif(findstr(PSTR("SUB=Submit"),buffer,bufferLen) != -1){

//Send HTTP data

HTTPSend(http_pageheader_ok,sizeof(http_pageheader_ok)-1,pSession,0);HTTPSend(Page1,sizeof(Page1)-1,pSession,1);httpSessionTable[i].status = HTTP_STATUS_IDLE;return;

}else if(findstr(PSTR("APPLY=Apply"),buffer,bufferLen) != -1){

HTTPSend(http_pageheader_ok,sizeof(http_pageheader_ok)-1,pSession,0);HTTPSend(Page1,sizeof(Page1)-1,pSession,1);httpSessionTable[i].status = HTTP_STATUS_IDLE;

}else{HTTPSend("",0,pSession,0);

}}

}}

Trong các hàm trên ta có gọi hàm của giao thức TCP nên các bạn include file "tcp.h" vào đầu file nhé:Code:#include "tcp.h"

Và nhớ thêm khai báo các hàm đã viết vào file header "http.h"

Để có thể biên dịch và khởi động webserver, ta cần sửa 1 ít trong chương trình chính.

Mở file "ntAVRnet.h", thêm vào các include 2 file "tcp.h" và "http.h":Code:#include "tcp.h"#include "http.h"

Thêm vào hàm netInit lệnh khởi động giao thức TCP và HTTP:Code:TCPInit();httpInit();

Thêm vào hàm main lệnh khởi động phiên TCP port 80 ở trạng thái listen cho dịch vụ HTTP (trước vòng lặp while(1)):Code:TCPCreateSession(80,httpDataIn);

Bây giờ các bạn có thể biên dịch và chạy thử, có thể truy cập web nhưng chưa có chức năng giám sát và điều khiển từ xa qua webserver, đợi bài tiếp theo nhé .

Thầy cho em hỏi câu này với:Hàm này trong file udp.c: void udpSend(unsigned long dstIp, unsigned int dstPort, unsigned int len, unsigned char* udpData)Còn câu lệnh gọi hàm này trong file dhcp.c:udpSend(DhcpServerIP, DHCP_UDP_SERVER_PORT, DHCP_UDP_CLIENT_PORT, DHCP_HEADER_LEN+3+6+6+1, (unsigned char*)packet);agrument (số biến gọi hàm không như nhau)của câu lệnh gọi hàm ko đúng, e ko bit sửa thế nào? Mong thầy giúp!cái này, thầy Tâm nói rõ là sửa lại rùi mà. bạn tìm kỹ lại xem, cụ thể như sau:void udpSend(unsigned long dstIp, unsigned int dstPort, unsigned int srcPort, unsigned int len, unsigned char* udpData){struct ntUDPHeader* udpHeader;udpHeader = (struct ntUDPHeader*)(udpData - UDP_HEADER_LEN);len += UDP_HEADER_LEN;udpHeader->desPort = HTONS(dstPort);udpHeader->srcPort = HTONS(srcPort);udpHeader->Len = HTONS(len);udpHeader->Checksum = 0;ipSend(dstIp, IP_PROTO_UDP, len, (unsigned char*)udpHeader);}

Thầy ơi, alij gặp vấn đề mà debug ko ra. Thầy coi hình em với. Đoạn code này cũng ko bit copy vào đâu cho đúng.TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1){//Tai nap gia tri timer 1TCNT1 = 0xFFFF - TIMER1_INTERVAL;//Cap nhat watchdog timerif((time_watchdog++) > 120){time_watchdog = 0;ethInit();}Counter1s++;arpTimer();TCPCheckTimeOut();////dhcpTimer();////}

Chia sẻ o

o

o

o

o

| Cảm ơn

Trả lời   Reply With Quote         24-11-11 00:22 #172 nttam79

Thành viên chính thức Tham gia

Feb 2009Bài viết

91Nguyên văn bởi dinh_dong

Thầy ơi, alij gặp vấn đề mà debug ko ra. Thầy coi hình em với. Đoạn code này cũng ko bit copy vào đâu cho đúng.

File Ðính Kèm 35926Đoạn code đó nằm trong file "timer.c"Nhớ include các file:Code:#include <avr/io.h>#include <avr/interrupt.h>

Kiểm tra lại có thể bạn copy trùng ở đâu đó.

Chia sẻ o

o

o

o

o

| Cảm ơn

dinh_dong đã cảm ơn nội dung này.Trả lời   Reply With Quote         24-11-11 00:25 #173 hieppro89

Thành viên chính thức Tham gia

Mar 2010Bài viết

35

Nhóm xã hội

5-Mở hàm ngắt timer 1 (trong file timer.h), thêm vào các hàm cập nhật thời gian của giao thức TCP và DHCP vào:Code:

TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1){//Tai nap gia tri timer 1TCNT1 = 0xFFFF - TIMER1_INTERVAL;//Cap nhat watchdog timerif((time_watchdog++) > 120){time_watchdog = 0;ethInit();}Counter1s++;arpTimer();TCPCheckTimeOut();////dhcpTimer();////}cái này đơn giản thui màsửa trong file Timer.c đóbạn để ý kĩ code TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1) cũ và TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1) mớikhi Thầy bảo sửa lại để cập nhật thời gian cho TCP và DHCPcó gì khác nhau ko?thì bác chỉ cần Copy cái TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1) mới đè lên cái cũ là ok ngay màđây là file timer.c của em://----------------------------------------------------------------------------// Writen by NTTam - PTITHCM//----------------------------------------------------------------------------#include <avr/io.h>#include <avr/interrupt.h>#include "ntAVRnet.h"#include "timer.h"#include "ethernet.h"#include "arp.h"#include "dhcp.h"#include "tcp.h"//#include "tcp.h"//----------------------------------------------------------------------------extern volatile unsigned int time_watchdog;static volatile unsigned long UptimeMs;static volatile unsigned char Counter10ms;static volatile unsigned int Counter1s;//----------------------------------------------------------------------------void timer1Init(void)

{// initialize timer 1// set prescaler on timer 1TCCR1B = (TCCR1B & ~TIMER_PRESCALE_MASK) | TIMER1PRESCALE; // set prescalerTCNT1H = 0; // reset TCNT1TCNT1L = 0;TIMSK |= (1<<TOIE1); // enable TCNT1 overflowTCNT1 = 0xFFFF - TIMER1_INTERVAL;}void timerInit(void){timer1Init();sei();}//! Interrupt handler for tcnt1 overflow interrupt//thêm vào các hàm ca^.p nha^.t tho+`i gian cu?a giao thu+'c TCP và DHCP TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1){//Tai nap gia tri timer 1TCNT1 = 0xFFFF - TIMER1_INTERVAL;//Cap nhat watchdog timerif((time_watchdog++) > 120){time_watchdog = 0;ethInit();}Counter1s++;arpTimer();TCPCheckTimeOut();////dhcpTimer();////} còn đây là file trc khi sửa đổi://----------------------------------------------------------------------------void timer1Init(void){// initialize timer 1// set prescaler on timer 1TCCR1B = (TCCR1B & ~TIMER_PRESCALE_MASK) | TIMER1PRESCALE; // set prescalerTCNT1H = 0; // reset TCNT1TCNT1L = 0;TIMSK |= (1<<TOIE1); // enable TCNT1 overflowTCNT1 = 0xFFFF - TIMER1_INTERVAL;}void timerInit(void){timer1Init();sei();

}//! Interrupt handler for tcnt1 overflow interruptTIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1){//Tai nap gia tri timer 1TCNT1 = 0xFFFF - TIMER1_INTERVAL;//Cap nhat watchdog timerif((time_watchdog++) > 120){time_watchdog = 0;ethInit();}Counter1s++;arpTimer();//TCPCheckTimeOut();} bác xem lại ở trang 6 và trang 13 để tiện so sánh nhébest regards!

Truy cập vào web:

Vậy là code ổn nhé, xào nấu thế nào là do các bạn thôi.

Thấy bé cún nhà mình dễ thương hông .

Mới đi dạo về thấy anh Tâm gởi code mừng wa nên thử liền.Ok anh ah, để em xem khác thế nào rồi nói sau. Nhưng sao code này không cần đăng nhập luônCòn phần LCD, ADC nhiệt độ hôm nào xong em port lên lun.File Ðính Kèm 36148À, biết tại sao rùi: trong chương trình chính (main()) mình quên gọi hàm httpInit để khởi động chuỗi mã hóa username/password.

Mọi người thêm vào nhé.