Thông báo

Collapse
No announcement yet.

Giao thức TCP/IP và Web server với AVR

Collapse
This is a sticky topic.
X
X
 
  • Lọc
  • Giờ
  • Show
Clear All
new posts

  • Giao thức TCP/IP và Web server với AVR

    Xin chào tất cả mọi người.
    Mình tham gia diễn đàn đã khá lâu rồi nhưng không có đóng góp gì cho diễn đàn, mà chôm từ diễn đàn thì khá nhiều. Hôm nay thấy cắn rứt lương tâm quá nên làm 1 cái tut đóng góp cho ĐTVN.
    Mình thấy đề tài giao tiếp TCP/IP với AVR và điều khiển qua Internet được khá nhiều bạn quan tâm, tài liệu trên các diễn đàn nước ngoài cũng khá nhiều nhưng chưa có tut nào cụ thể và chi tiết, đặc biệt là hướng dẫn để mọi người có thể tự viết code được, chứ không phải copy code có sẵn. Vì vậy nên mình lôi 1 project đã làm cách đây khá lâu ra để làm cơ sở cho tut này.
    Bài 1-Hardware:
    Trong project này mình sử dụng một con ENC28J60 của Microchip để giao tiếp Ethernet. Vi điều khiển thì sử dụng Atmega32, có bộ nhớ RAM 2k, vừa đủ dùng (vì 1 frame ethernet có chiều dài max 1500bytes).
    Mọi người có thể mua 1 module phát triển Atmega32 + ENC28J60 tại TME (ATMEGA32 - Ethernet board - TMe: Linh kien, Sensors, Kit Phat trien, Mach nap 8051, PIC, AVR, ARM, EPROM, Flash...). Hay tự build 1 mạch theo sơ đồ sau:


    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.

    Bài 2-Cở sở giao thức và thiết kế lưu đồ dữ liệu-Tạo project trong AVR studio:
    Chồng giao thức TCP/IP:
    Dưới đây là mô hình chồng giao thức TCP/IP


    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,…)
    Toàn bộ code viết trên AVR Studio, biên dịch với AVR-GCC

    Ta tạo 1 project mới trong AVR studio:

    Set thuộc tính cho project và chọn CPU, chọn tần số thạch anh:


    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
    Last edited by nttam79; 01-11-2011, 21:31.

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

    Mở file enc28j60.h, khai báo địa chỉ các thanh ghi vào file
    Code:
    //----------------------------------------------------------------------------
    // Writen by NTTam - PTITHCM
    //----------------------------------------------------------------------------
    #ifndef ENC28J60_H
    #define ENC28J60_H
    
    // ENC28J60 Control Registers
    // Control register definitions are a combination of address,
    // bank number, and Ethernet/MAC/PHY indicator bits.
    // - Register address		(bits 0-4)
    // - Bank number		(bits 5-6)
    // - MAC/PHY indicator	(bit 7)
    #define ADDR_MASK	0x1F
    #define BANK_MASK	0x60
    #define SPRD_MASK	0x80
    // All-bank registers
    #define EIE			0x1B
    #define EIR			0x1C
    #define ESTAT		0x1D
    #define ECON2		0x1E
    #define ECON1		0x1F
    // Bank 0 registers
    #define ERDPTL		(0x00|0x00)
    #define ERDPTH		(0x01|0x00)
    #define EWRPTL		(0x02|0x00)
    #define EWRPTH		(0x03|0x00)
    #define ETXSTL		(0x04|0x00)
    #define ETXSTH		(0x05|0x00)
    #define ETXNDL		(0x06|0x00)
    #define ETXNDH		(0x07|0x00)
    #define ERXSTL		(0x08|0x00)
    #define ERXSTH		(0x09|0x00)
    #define ERXNDL		(0x0A|0x00)
    #define ERXNDH		(0x0B|0x00)
    #define ERXRDPTL	(0x0C|0x00)
    #define ERXRDPTH	(0x0D|0x00)
    #define ERXWRPTL	(0x0E|0x00)
    #define ERXWRPTH	(0x0F|0x00)
    #define EDMASTL		(0x10|0x00)
    #define EDMASTH		(0x11|0x00)
    #define EDMANDL		(0x12|0x00)
    #define EDMANDH		(0x13|0x00)
    #define EDMADSTL	(0x14|0x00)
    #define 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 ENC28J60
    Tiếp theo ta viết tiếp 2 hàm gửi và nhận 1 gói tin:
    Code:
    void enc28j60PacketSend(unsigned int len, unsigned char* packet)
    {
    	enc28j60Write(EWRPTL, TXSTART_INIT);
    	enc28j60Write(EWRPTH, TXSTART_INIT>>8);
    	enc28j60Write(ETXNDL, (TXSTART_INIT+len));
    	enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);
    	enc28j60SPIWrite(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
    
    	enc28j60WriteBuffer(len, packet);
    	enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
    }
    
    unsigned int NextPacketPtr;
    
    unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
    {
    	unsigned int rxstat;
    	unsigned int len;
    	if( !enc28j60Read(EPKTCNT) )
    		return 0;
    	enc28j60Write(ERDPTL, (NextPacketPtr));
    	enc28j60Write(ERDPTH, (NextPacketPtr)>>8);
    	NextPacketPtr  = enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0);
    	NextPacketPtr |= ((unsigned int)enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0))<<8;
    	len  = enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0);
    	len |= ((unsigned int)enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0))<<8;
    	rxstat  = enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0);
    	rxstat |= ((unsigned int)enc28j60SPIRead(ENC28J60_READ_BUF_MEM, 0))<<8;
    	len = ((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 lo
    	ETXSTH, HI8(TXSTART_INIT), //start hi
    	ETXNDL, LO8(TXSTOP_INIT  ), //end lo
    	ETXNDH, HI8(TXSTOP_INIT  ), //end hi
    
    	ERXSTL, LO8(RXSTART_INIT), //start lo
    	ERXSTH, HI8(RXSTART_INIT), //start hi
    	ERXNDL, LO8(RXSTOP_INIT  ), //end lo
    	ERXNDH, 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 duplex
    	MAIPGL,  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 = IN
    	SPCR = (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 frames
    	enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
    	enc28j60SPIWrite(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
    	enc28j60PhyWrite(PHLCON, 0x347A); 
    }
    Cuối cùng, nhớ thêm phần khai báo tất cả các hàm đã viết trên vào file header (enc28j60.h) điều này sẽ giúp truy xuất dễ dàng các hàm này từ các module khác.
    Bài tiếp theo sẽ là lập trình giao thức ethernet.
    Thanks dinh_dong
    Last edited by nttam79; 02-11-2011, 01:38.

    Comment


    • #3
      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;
      }
      Last edited by dinh_dong; 01-11-2011, 18:38.

      Comment


      • #4
        Cám ơn em đã hướng dẫn
        Em là SV PTITHCM, chắc là D08 hay D09DTAx hả? Anh đã từng là giảng viên khoa Viễn thông, PTITHCM. Em có thể hỏi các thầy bên khoa Điện tử, ai cũng biết anh cả. Hiện nay anh đã nghỉ công tác tại HV, em có thể liên lạc qua email nttam79@gmail.com, hay liên hệ khoa Viễn thông để xin số ĐT, anh không muốn post số ĐT lên diễn đàn.
        Project này anh làm đã lâu rồi, từ thời còn công tác tại HV, nên trong code và schematic vẫn để PTITHCM.
        Anh sẽ tiếp tục hướng dẫn cho đến thiết kế và build website trên AVR, nhưng tranh thủ thời gian rảnh mới post bài được, thông cảm nhé.
        Last edited by nttam79; 01-11-2011, 21:33.

        Comment


        • #5
          Nguyên văn bởi nttam79 Xem bài viết
          Cám ơn em đã hướng dẫn
          Em là SV PTITHCM, chắc là D08 hay D09DTAx hả? Anh đã từng là giảng viên khoa Viễn thông, PTITHCM. Em có thể hỏi các thầy bên khoa Điện tử, ai cũng biết anh cả. Hiện nay anh đã nghỉ công tác tại HV, em có thể liên lạc qua email nttam79@gmail.com, hay liên hệ khoa Viễn thông để xin số ĐT, anh không muốn post số ĐT lên diễn đàn.
          Project này anh làm đã lâu rồi, từ thời còn công tác tại HV, nên trong code và schematic vẫn để PTITHCM.
          Anh sẽ tiếp tục hướng dẫn cho đến thiết kế và build website trên AVR, nhưng tranh thủ thời gian rảnh mới post bài được, thông cảm nhé.
          Dạ, em khóa Đ 07, khóa đầu của KHoa ĐT, xin lỗi vì gọi Anh, cứ để em gọi là Thầy ạ. Chắc cùng thời gian làm với thầy N.T.Hải, du học ở Úc đúng không ạ.
          Oke, có gì em sẽ hỏi qua email.
          Tks!

          Comment


          • #6
            Nguyên văn bởi dinh_dong Xem bài viết
            Dạ, em khóa Đ 07, khóa đầu của KHoa ĐT, xin lỗi vì gọi Anh, cứ để em gọi là Thầy ạ. Chắc cùng thời gian làm với thầy N.T.Hải, du học ở Úc đúng không ạ.
            Oke, có gì em sẽ hỏi qua email.
            Tks!
            Tôi biết hết các thầy trong khoa Điện tử, trước khi nghỉ tôi đã có 7 năm giảng dạy tại khoa VT. Tôi nghỉ sau anh Hải khoảng 6 tháng.

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

            Trước hết, ta cần nắm rõ cấu trúc của tất cả các gói tin của mỗi giao thức để có thể xử lý được thông tin chứa trong nó.
            Ta tạo 1 file header mới là “packet.h”, file này sẽ dùng để chứa mô tả cấu trúc của tất cả các gói tin của các giao thức sử dụng trong project này:
            Code:
            //----------------------------------------------------------------------------
            // Writen by NTTam - PTITHCM
            //----------------------------------------------------------------------------
            #ifndef PACKET_H
            #define PACKET_H
            
            #endif //PACKET_H
            Trước hết ta tìm hiểu cấu trúc 1 frame ethernet:

            Như vậy 1 frame ethernet bắt đầu bắng các byte Preamble để dồng bộ và 1 byte Start of Frame để xác định đầu frame (phần này sẽ được ENC28J60 tự động lược bỏ, ta không cần quan tâm). Tiếp theo là địa chỉ MAC của host nhận (destination address), địa chỉ MAC của host gửi (source address), mỗi địa chỉ MAC này gồm 6 byte. Kế đến là 2 byte length (cho biết chiều dài) hoặc type (cho biết dữ liệu chứa trong frame là loại dữ liệu của giao thức lớp trên nào). Kế đến là dữ liệu. Cuối cùng là phần kiểm tra lỗi (FCS), phần này cũng được ENC28J60 xử lý, ta không quan tâm.
            Như vậy ta cần khai báo cấu trúc của header frame ethernet (từ phần địa chỉ đến 2 byte type) trong file “packet.h”
            Trước hết ta định nghĩa 1 struc cho địa chỉ MAC:
            Code:
            //--------------------------------------------------------------------------------------
            //Dia chi vat ly hay dia chi MAC (lop Ethernet)
            struct ntEthAddr
            {
            	unsigned char addr[6];
            };
            Kế đến là header của frame ethernet:
            Code:
            //--------------------------------------------------------------------------------------
            //Ethernet header
            //  Gom 14 byte:
            //  06 byte dia chi dich
            //  06 byte dia chi nguon
            //  02 byte type (cho biet frame ethernet mang ben trong loai du lieu gi)
            #define ETH_HEADER_LEN	14
            struct ntEthHeader
            {
            	struct ntEthAddr desAddr;
            	struct ntEthAddr srcAddr;
            	unsigned int type;
            };
            Ta cũng định nghĩa các giá trị hằng qui định cho trường Type (tham khảo tài liệu về ethernet để biết thêm các giá trị của trường này):
            Code:
            //
            #define ETH_TYPE_ARP 0x0806
            #define ETH_TYPE_IP  0x0800
            #define ETH_TYPE_IP6 0x86dd
            Nhân tiện ở đây cũng định nghĩa luôn cấu trúc của các frame ethernet cho VLAN (802.1q) và VLAN Q-inQ (802.1ad), cấu trúc của header MPLS. Đây là các giao thức sữ dụng phổ biến trên mạng MEN (hay MAN-E) của VNPT. Bạn nào không quan tâm đến các giao thức này có thể bỏ qua, ta chỉ tập trung vào giao thức ethernet thuần túy.
            Code:
            //--------------------------------------------------------------------------------------
            //Ethernet header 802.1q VLAN Tagging
            struct ntEth802_1qHeader
            {
            	struct ntEthAddr desAddr;
            	struct ntEthAddr srcAddr;
            	unsigned int type;
            	unsigned int TPID;
            	unsigned int PCP_CFI_VID;
            };
            #define ETH_802_1Q_HEADER_LEN	18
            //
            #define ETH_802_1Q_TPID			0x8100
            #define ETH_802_1Q_PCP_MASK		0xE000
            #define ETH_802_1Q_CFI_MASK		0x1000
            #define ETH_802_1Q_VID_MASK		0x0FFF
            //--------------------------------------------------------------------------------------
            //Ethernet header 802.1ad Q-in-Q VLAN Tagging
            struct ntEth802_1adHeader
            {
            	struct ntEthAddr desAddr;
            	struct ntEthAddr srcAddr;
            	unsigned int type;
            	unsigned int OuterTPID;
            	unsigned int OuterPCP_CFI_VID;
            	unsigned int InnerTPID;
            	unsigned int InnerPCP_CFI_VID;
            };
            #define ETH_802_1AD_HEADER_LEN		22
            //
            #define ETH_802_1AD_TPID			0x88a8
            #define ETH_802_QINQ_TPID1			0x9100
            #define ETH_802_QINQ_TPID2			0x9200
            #define ETH_802_QINQ_TPID3			0x9300
            //--------------------------------------------------------------------------------------
            //Cau truc MPLS Header
            struct ntMPLSHeader
            {
            	unsigned int	HighLabelValue;
            	unsigned char	TrafficClass_Stack;
            	unsigned char	TTL;
            };
            #define MPLS_HEADER_LEN			4
            //
            #define MPLS_LOW_LABEL_MASK		0xF0
            #define MPLS_TRF_CLS_MASK		0x0E
            #define MPLS_STACK_MASK			0x01
            //
            Vậy là xong phần khai báo cấu trúc frame ethernet. Tiếp theo là viết các hàm xử lý giao thức ethernet.
            Trong chồng giao thức TCP/IP, giao thức ethernet đóng vai trò lớp truy nhập và truyền dẫn. Việc gửi và nhận dữ liệu ở lớp ethernet được thực hiện dựa vào địa chỉ vật lý hay còn gọi là địa chỉ MAC.
            Trong mỗi frame ethernet đều chứa 2 địa chỉ MAC: một địa chỉ của host gửi và 1 địa chỉ của host nhận
            khi lớp ethernet nhận được 1 frame dữ liệu, trước hết nó sẽ kiểm tra địa chỉ host nhận xem có phải là địa chỉ của nó không (tức là gửi cho nó), nếu đúng nó sẽ nhận frame này và chuyển đến lớp IP. Ngoài ra còn có 1 trường hợp nữa lớp ehternet sẽ nhận frame: đó là nếu địa chỉ host nhận là địa chỉ broadcast (tức là gửi cho tất cả mọi máy trong mạng LAN), trong trường hợp này frame sẽ được nhận và xử lý.
            Ngoài việc kiểm tra địa chỉ, trong frame ethernet còn có 1 trường chứa mã kiểm tra lỗi giúp phát hiện những lỗi xảy ra trong quá trình truyền, các frame bị xác định là có lỗi sẽ bị bỏ qua.
            Trong mạch của chúng ta, việc kiểm tra lỗi và kiểm tra địa được thực hiện tự động bởi IC ENC28J60, do đó ta không cần lập trình cho các chức năng này. Mỗi khi nhận được 1 frame trên đường truyền, ENC28J60 sẽ kiểm tra lỗi xem có sai sót không, tiếp đó nó sẽ đối chiếu địa chỉ host nhận với địa chỉ đã được cấu hình cho nó (trong các thanh ghi địa chỉ MAC: MAADR0-5). Nếu không có lỗi và địa chỉ là gửi cho nó, nó sẽ tạo 1 ngắt cứng (trên chân INT của ENC28J60) để báo cho VĐK biết là nó vừa nhận được 1 frame hợp lệ và yêu cầu VĐK xử lý frame này.
            Vậy công việc của chúng ta là viết hàm xử lý cho trường hợp này, cũng như cung cấp 1 hàm gửi đi 1 frame dữ liệu (để sử dụng khi muốn gửi dữ liệu đi). Bên cạnh đó ta cũng cần một số hàm cung cấp các chức năng bổ sung như set/get địa chỉ MAC,…
            Tạo 1 file source "ethernet.c" để viết module ethernet và file header cho nó "ethernet.h"
            File ethernet.c
            Code:
            //----------------------------------------------------------------------------
            // Writen by NTTam - PTITHCM
            //----------------------------------------------------------------------------
            #include <avr/io.h>
            #include <avr/pgmspace.h>
            #include "packet.h"
            #include "enc28j60.h"
            #include "enc28j60conf.h"
            #include "ethernet.h"
            File ethernet.h
            Code:
            //----------------------------------------------------------------------------
            // Writen by NTTam - PTITHCM
            //----------------------------------------------------------------------------
            #ifndef ETHERNET_H
            #define ETHERNET_H
            
            #endif //ETHERNET_H
            Buồn ngủ quá, mai viết tiếp....
            Last edited by nttam79; 02-11-2011, 01:52.

            Comment


            • #7
              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 Ethernet
              void ethInit(void)
              {
              	enc28j60Init();
              	ETH_INT_ENABLE;
              }
              //--------------------------------------------------------------------------------------
              //Ham goi 1 frame xuong chip ethernet
              void ethSendFrame(unsigned int len, unsigned char* packet)
              {
              	enc28j60PacketSend(len, packet);
              }
              //--------------------------------------------------------------------------------------
              //Ham doc 1 frame ethernet tu chip ethernet ve buffer tren RAM cua CPU
              unsigned int ethGetFrame(unsigned int maxlen, unsigned char* packet)
              {
              	return enc28j60PacketReceive(maxlen, packet);
              }
              //--------------------------------------------------------------------------------------
              //Ham doc dia chi MAC hien tai tu chip ethernet, luu vao buffer macaddr[6]
              void ethGetMacAddress(unsigned char* macaddr)
              {
              	*macaddr++ = 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 ethernet
              void ethSetMacAddress(unsigned char* macaddr)
              {
              	enc28j60Write(MAADR5, *macaddr++);
              	enc28j60Write(MAADR4, *macaddr++);
              	enc28j60Write(MAADR3, *macaddr++);
              	enc28j60Write(MAADR2, *macaddr++);
              	enc28j60Write(MAADR1, *macaddr++);
              	enc28j60Write(MAADR0, *macaddr++);
              }
              //--------------------------------------------------------------------------------------
              //Ham tra lai con tro den buffer ethernet (tren RAM cua CPU)
              unsigned char* ethGetBuffer(void)
              {
              	return ethBuffer;
              }
              Nhắc lại là để truy xuất được đến các hàm trong module “enc28j60.c”, đến đây các bạn nhất thiết phải thêm các khai báo hàm đã viết trong “enc28j60.c” vào file header “enc28j60.h”. Tức là trong file “enc28j60.h” phải có các dòng khai báo này:
              Code:
              //----------------------------------------------------------------------------
              unsigned char enc28j60SPIRead(unsigned char op, unsigned char address);
              void enc28j60SPIWrite(unsigned char op, unsigned char address, unsigned char data);
              void enc28j60SetBank(unsigned char address);
              void enc28j60ReadBuffer(unsigned int len, unsigned char* data);
              void enc28j60WriteBuffer(unsigned int len, unsigned char* data);
              unsigned char enc28j60Read(unsigned char address);
              void enc28j60Write(unsigned char address, unsigned char data);
              unsigned int enc28j60PhyRead(unsigned char address);
              void enc28j60PhyWrite(unsigned char address, unsigned int data);
              void enc28j60PacketSend(unsigned int len, unsigned char* packet);
              unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet);
              void delay_us(unsigned short time_us);
              void enc28j60Init(void);
              Tiếp theo ta viết phần xử lý khi ENC28J60 nhận được 1 frame dữ liệu hợp lệ.
              Như đã nói trước đó, khi ENC28J60 nhận được 1 frame dữ liệu hợp lệ, nó sẽ tạo 1 ngắt để báo cho VĐK biết và xử lý dữ liệu nhận được này. Như vậy ta cần viết một ISR ngắt tương ứng cho sự kiện đó:
              Code:
              //--------------------------------------------------------------------------------------
              //Vector ngat cua ethernet, mot ngat ngoai se duoc khoi tao boi chip ethernet
              // moi khi no nhan duoc 1 frame ethernet (dung dia chi cua no)
              ISR (ETH_INTERRUPT)
              {
              eth_got_frame = 1;
              time_watchdog = 0;
              	ETH_INT_DISABLE;
              }
              Trong hàm này ta có sử dụng định nghĩa vector ngắt (ISR), do đó ta cần include file tương ứng vào. Thêm dòng này vào đầu file "ehternet.c"
              Code:
              #include <avr/interrupt.h>
              Trong hàm này, ta sử dụng 2 biến toàn cục là eth_got_frame và time_watchdog, hai biến này cần được khai báo trong file “enc28j60.c” (nên để ở dầu file)
              Code:
              unsigned char eth_got_frame = 0;
              volatile unsigned int time_watchdog;
              Giải thích: thay vì viết toàn bộ phần xử lý trong hàm ngắt (ISR), trong hàm ngắt ta chỉ đơn giản là set biến eth_got_frame = 1 để báo cho biết có 1 frame đang chờ xử lý, biến này (eth_got_frame) sẽ liên tục được kiểm tra bởi hàm dịch vụ ethernet (ethService) mà ta sẽ viết sau đây, hàm này (ethService) sẽ liên tục được gọi trong 1 vòng lặp ở chương trình chính (hàm main trong file “ntAVRnet.c”) để thực thi liên tục.
              Cách viết này nhằm tránh xảy ra hiện tượng ngắt chồng ngắt, có thể dẫn đến chương trình thực thi không đúng mong muốn do nội dung hàm ngắt quá dài, chưa thực thi xong đã xảy ra 1 ngắt khác.
              Còn biến time_watchdog là một biến nhằm phát hiện các lỗi dẫn đến treo các giao thức mạng hoặc IC ENC28J60. Biến này sẽ được tăng liên tục bởi timer nhưng lại được reset về 0 mỗi khi nhận được 1 frame ethernet mới, điều này cho phép phát hiện 1 khoảng thời gian quá lâu mà ta không nhận được frame ethernet nào (khi biến time_watchdog tăng đến một giá trị ngưỡng), khi đó ta giả thiết là ENC28J60 bị treo hay phát sinh lỗi, lúc đó ta sẽ gọi hàm reset IC này và khởi động lại giao thức ethernet. Điều này trong thực tế rất hữu ích, nó giúp mạch của chúng ta chạy ổn định hơn rất nhiều.
              Đồng thời ta cũng disable ngắt ngoài này trong thời gian chờ xử lý frame.

              Vậy ta viết tiếp phần xử lý frame ethernet trong hàm ethService như sau:
              Code:
              //--------------------------------------------------------------------------------------
              //Ham duoc goi lien tuc de thuc thi cac tac vu cua giao thuc ethernet
              void ethService(void)
              {
              	int len;
              	struct ntEthHeader* ethPacket;
              	if(!eth_got_frame) return;
                      eth_got_frame = 0;
              	// look for a packet
              	len = ethGetFrame(ETHERNET_BUFFER_SIZE, ethBuffer);
              
              	if(len)
              	{
              		ethPacket = (struct ntEthHeader*)&ethBuffer[0];
              
              		#ifdef ETH_DEBUG
              		printf("Received packet len: %d, type:", len);
              		#endif
              		
              		if(ethPacket->type == HTONS(ETH_TYPE_IP))
              		//Neu day la frame danh cho giao thuc IP
              		{
              			#ifdef ETH_DEBUG
              			printf("IP packet\r\n");
              			#endif
              			arpIPPacketIn((unsigned char*)&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_DEBUG
              			printf("ARP packet\r\n");
              			#endif
              			arpArpProcess(len, ethBuffer );
              		}else{
              			#ifdef ETH_DEBUG
              			printf("Unknown packet:%x\r\n",ethPacket->type);
              			#endif
              			ethInit();
              		}
              		ETH_INT_ENABLE;
              	}
              	return;
              }
              //--------------------------------------------------------------------------------------
              Giải thích:
              Hàm này khi phát hiện có frame mới (biến eth_got_frame khác 0) thì tiến hành kiểm tra trường Type trong header frame ethernet để xem dữ liệu chứa trong frame là của giao thức nào (IP hay ARP) và sẽ gọi hàm tương ứng của giao thức đó để xử lý.
              Có một lưu ý quan trọng là trong trình biên dịch gcc (cũng như các trình biên dịch ngôn ngữ C khác cho AVR), đối với các biến có kích thước lớn hơn 1 byte (int, double, long,…) thì thứ tự các byte trong bộ nhớ của AVR được sắp xếp theo thứ tự ngược lại với thứ tự trong các header của gói tin (frame ethernet, IP packet,…). Do đó khi đọc các biến này ra từ buffer ethernet cũng như trước khi ghi vào buffer, ta phải đổi thứ tự các byte này. Ta viết một số macro cho mục đích này và lưu luôn trong file “packet.h” để sử dụng sau này.
              Code:
              //--------------------------------------------------------------------------------------
              #define HTONS(s)		((s<<8) | (s>>8))	//danh cho bien 2 byte
              #define HTONL(l)		((l<<24) | ((l&0x00FF0000l)>>8) | ((l&0x0000FF00l)<<8) | (l>>24))	//danh cho bien 4 byte
              //
              Lưu ý là trong hàm này ta có gọi các hàm của giao thức lớp trên (IP và ARP). Đó là các hàm arpIPPacketIn, IPProcess, và arpArpProcess. Các hàm này ta vẫn chưa viết nên khi biên dịch file “ethernet.c” sẽ có báo lỗi thiếu các hàm này.
              Để cho mục đích debug sau này, trong hàm ta có sử dụng hàm xuất ra cổng serial là printf. Hàm này các bạn tự viết lấy dùng uart nhé. Trong nội dung hướng dẫn này tập trung vào giao thức TCP/IP thôi.
              Sau khi xử lý xong frame, ta cần enable ngắt ngoài trở lại.

              Đến đây là cơ bản xong giao thức ethernet, tiếp theo sẽ là giao thức IP và ARP, rắc rối hơn nhiều.
              Last edited by nttam79; 03-11-2011, 00:25. Lý do: copy thiếu 1 dòng trong hàm ethService, mới bổ sung

              Comment


              • #8
                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".
                Last edited by nttam79; 03-11-2011, 10:50.

                Comment


                • #9
                  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 header
                  struct 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 header
                  struct 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é.
                  Last edited by nttam79; 02-11-2011, 23:27.

                  Comment


                  • #10
                    Chào anh ạh !.
                    Em cũng đang tìm hiểu web sever nhúng nhưng em đang làm trên PIC32 và sử dụng TCP/IP Stack của Microchip. Anh cho em hỏi một chút ah:
                    Nếu em muốn lưu một trang web vào một flash ROM để sau khi ta bật trình duyệt web lên thì có thể truy cập và điều khiển board này thì cơ chế của nó là gì ạ mong anh chỉ giùm ?.
                    Cảm ơn anh thật nhiều !.

                    Comment


                    • #11
                      Nguyên văn bởi vip_co_don Xem bài viết
                      Chào anh ạh !.
                      Em cũng đang tìm hiểu web sever nhúng nhưng em đang làm trên PIC32 và sử dụng TCP/IP Stack của Microchip. Anh cho em hỏi một chút ah:
                      Nếu em muốn lưu một trang web vào một flash ROM để sau khi ta bật trình duyệt web lên thì có thể truy cập và điều khiển board này thì cơ chế của nó là gì ạ mong anh chỉ giùm ?.
                      Cảm ơn anh thật nhiều !.
                      Chào em.
                      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.
                      Last edited by nttam79; 03-11-2011, 10:26.

                      Comment


                      • #12
                        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
                        Trường hợp 1:khi lớp ethernet nhận được 1 frame dữ liệu, trước hết nó sẽ kiểm tra địa chỉ host nhận xem có phải là địa chỉ của nó không (tức là gửi cho nó), nếu đúng nó sẽ nhận frame này và chuyển đến lớp IP
                        .
                        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 ạ?

                        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ý.
                        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?

                        Comment


                        • #13
                          Hay quá bác ạ, em đang làm đồ án tốt nghiệp về cái này nhưng dùng fpga cơ ạ. Có j mong bác giúp đỡ về lý thuyết Ethernet.
                          Thank bác nhiều.

                          Comment


                          • #14
                            Nguyên văn bởi dinh_dong Xem bài viết
                            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é.
                            Last edited by nttam79; 04-11-2011, 11:23.

                            Comment


                            • #15
                              Nguyên văn bởi robocon2011 Xem bài viết
                              Hay quá bác ạ, em đang làm đồ án tốt nghiệp về cái này nhưng dùng fpga cơ ạ. Có j mong bác giúp đỡ về lý thuyết Ethernet.
                              Thank bác nhiều.
                              Tôi cũng có làm về FPGA trên chip của Xilinx với ngôn ngữ VHDL và Verilog, nếu cần em cứ hỏi

                              Comment

                              Về tác giả

                              Collapse

                              nttam79 Tìm hiểu thêm về nttam79

                              Bài viết mới nhất

                              Collapse

                              Đang tải...
                              X