准备使用W5100

 都说它简单易用,买了ic和模块。找了例程和资料,准备上马.

CSDN上面的AVR测试程序:http://download.csdn.net/source/1935336

 

The rapid penetration of the internet networks into many of today’s modern homes and personal gadgets (e.g. smart phone and smart pads) opening a tremendous useful and interesting embedded system application that could be integrated into our house or known as the intelligent house. For example by putting a small embedded system web server in our house, we could easily monitor such as alarm, temperature or even turn on/off the lamp or the garden’s water sprinkle; eventually from any remote location through the wireless personal gadget; Or perhaps you just want to impress your relative or friend with a very accurate digital clock which automatically synchronized the time through the Network Time Protocol (NTP) over the internet at your home or office.

All of these interesting and challenging embedded system applications could be accomplished by integrating the Ethernet protocol which is formed the basic of the communication protocol used in the internet into the embedded system. Currently there are several approaches for this solution but basically they could be divided into two categories wired e.g. Wiznet W5100, W5300, Microchip ENC28J60 and wireless such as the Microchip ZG2100MC Wi-Fi module (recently is acquired by Microchip from the Zero-G Wireless on Jan 11, 2010).

On this tutorial we are going to build the embedded web server using the Wiznet WIZ811MJ network module which is based on the Wiznet well known W5100 TCP/IP hardwired chip that include the ethernet controller physical layer (PHY). The WIZ811MJ network module comes with the Wiznet W5100 chip, MAG-JACK (RJ45) together with the glued logic needed to communicate with the microcontroller through the SPI or bus interface.

The reason I choose the Wiznet 5100 based chip on this tutorial because this chip has the TCP/IP hardwired on it; therefore it will make developing the TCP/IP protocol stack based application much easier and could be implemented on the small RAM size microcontroller class compared to the firmware TCP/IP protocols stack based implementation approach (you don’t have to know everything about the TCP/IP protocol stack in order to be able to use this chip). The other reason is because the Wiznet 5100 chip has been around for a few years in the market and has already being matured. This chip is used in many commercial applications such as the Arduino framework on their standard Arduino Ethernet shield as shown on this following picture.

Ok, now lets list down the necessary electronics components and supported software for this tutorial and make sure you have the AVR ATMega328 microcontroller datasheet near you:

  • Resistors: 10K Ohm (1), 1K Ohm (1) and 470 Ohm (2)
  • Capacitors: 10uF/16v (2) and 0.1uF (2)
  • LEDS: 3 mm Blue LED (2)
  • Transistor: 2N3904 (1)
  • Voltage Regulator IC: LM1086 - 3.3 Volt
  • One momentary push button
  • One 30×60 mm Prototype board
  • Two 10 pins male double header and 5 pins male single header
  • One 2 pins male single polarized header
  • AVRJazz Mega328 board and JazzMate 5 Volt voltage regulator board from ermicro
  • Wiznet WIZ811MJ Network Module
  • Atmel AVR Studio version 4.17 IDE
  • WinAVR AVR-GCC 4.3.2; avr-libc 1.6.6 (WinAVR 20090313)
  • Reference Document: W5100 Datasheet, WIZ811MJ Datasheet, W5100 Porting Guide, Atmel AVR ATMega328 Datasheet.

The Wiznet W5100 Hardwired TCP/IP Protocol Chip

Basically the Wiznet W5100 implements a full-featured of standard IEEE 802.3 (Ethernet physical and data link layer) and powerful TCP/IP stack inside the chip; this make the Wiznet W5100 chip is suitable choice for integrating the embedded system into the internet. Programming the Wiznet W5100 chip is also easier as we just need to write and read to and from the W5100 internal registers in order to use the build-in TCP/IP protocol features.

The Wiznet W5100 chip come with three method of controlling its internal registers; this first two is to use the parallel direct or indirect bus, the last one is to use a well known embedded serial data transfer known as the SPI (serial peripheral interface), on this tutorial we are going to use the SPI to control the Wiznet W5100 chip. You could read more about SPI on my previous posted blog Using Serial Peripheral Interface (SPI) Master and Slave with Atmel AVR Microcontroller. The basic SPI connection between the Wiznet WIZ811MJ network module and Atmel AVR ATMega328 microcontroller is shown on this following picture.

The Wiznet W5100 will act as the SPI slave device controlled by Atmel AVR ATMega328 microcontroller as the SPI Master. The SPI protocol need at least four signal i.e. MOSI (Master Out Serial In), MISO (Master In Serial Out), SCK (signal clock provided by the master) and CS (the SPI slave chip select). Although the AVR ATMega328 microcontroller support all the SPI modes (i.e. 0,1,2 and 3) but the Wiznet W5100 chip support the most SPI common modes (mode 0 and mode 3) where it will sample a data on rising edge clock and outputting on the falling edge clock. The W5100 chip also provides the interrupt pin, but on this tutorial we don’t use the interrupt feature, instead we use a pooling method to control the W5100 operation.

In order to understand of how we control the Wiznet W5100 as the SPI slave device, we are going to create the W5100 initialization program; so it will response to the simple “ping” network command (ICMP protocol). The following is the complete C code called “wiznetping.c” for initializing the Wiznet W5100 chip.

/*****************************************************************************
//  File Name    : wiznetping.c
//  Version      : 1.0
//  Description  : Wiznet W5100
//  Author       : RWB
//  Target       : AVRJazz Mega168 Board
//  Compiler     : AVR-GCC 4.3.2; avr-libc 1.6.6 (WinAVR 20090313)
//  IDE          : Atmel AVR Studio 4.17
//  Programmer   : AVRJazz Mega168 STK500 v2.0 Bootloader
//               : AVR Visual Studio 4.17, STK500 programmer
//  Last Updated : 01 July 2010
*****************************************************************************/
#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include <util/delay.h>

#define BAUD_RATE 19200
// AVRJazz Mega168/328 SPI I/O
#define SPI_PORT PORTB
#define SPI_DDR  DDRB
#define SPI_CS   PORTB2
// Wiznet W5100 Op Code
#define WIZNET_WRITE_OPCODE 0xF0
#define WIZNET_READ_OPCODE 0x0F
// Wiznet W5100 Register Addresses
#define MR   0x0000   // Mode Register
#define GAR  0x0001   // Gateway Address: 0x0001 to 0x0004
#define SUBR 0x0005   // Subnet mask Address: 0x0005 to 0x0008
#define SAR  0x0009   // Source Hardware Address (MAC): 0x0009 to 0x000E
#define SIPR 0x000F   // Source IP Address: 0x000F to 0x0012
#define RMSR 0x001A   // RX Memory Size Register
#define TMSR 0x001B   // TX Memory Size Register
void uart_init(void)
{
  UBRR0H = (((F_CPU/BAUD_RATE)/16)-1)>>8;	// set baud rate
  UBRR0L = (((F_CPU/BAUD_RATE)/16)-1);
  UCSR0B = (1<<RXEN0)|(1<<TXEN0); 		// enable Rx & Tx
  UCSR0C=  (1<<UCSZ01)|(1<<UCSZ00);  	       // config USART; 8N1
}
void uart_flush(void)
{
  unsigned char dummy;
  while (UCSR0A & (1<<RXC0)) dummy = UDR0;
}
int uart_putch(char ch,FILE *stream)
{
   if (ch == '\n')
    uart_putch('\r', stream);
   while (!(UCSR0A & (1<<UDRE0)));
   UDR0=ch;
   return 0;
}
int uart_getch(FILE *stream)
{
   unsigned char ch;
   while (!(UCSR0A & (1<<RXC0)));
   ch=UDR0;  

   /* Echo the Output Back to terminal */
   uart_putch(ch,stream);       

   return ch;
}
void ansi_cl(void)
{
  // ANSI clear screen: cl=\E[H\E[J
  putchar(27);
  putchar('[');
  putchar('H');
  putchar(27);
  putchar('[');
  putchar('J');
}
void ansi_me(void)
{
  // ANSI turn off all attribute: me=\E[0m
  putchar(27);
  putchar('[');
  putchar('0');
  putchar('m');
}
void SPI_Write(unsigned int addr,unsigned char data)
{
  // Activate the CS pin
  SPI_PORT &= ~(1<<SPI_CS);
  // Start Wiznet W5100 Write OpCode transmission
  SPDR = WIZNET_WRITE_OPCODE;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address High Bytes transmission
  SPDR = (addr & 0xFF00) >> 8;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address Low Bytes transmission
  SPDR = addr & 0x00FF;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));   

  // Start Data transmission
  SPDR = data;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // CS pin is not active
  SPI_PORT |= (1<<SPI_CS);
}
unsigned char SPI_Read(unsigned int addr)
{
  // Activate the CS pin
  SPI_PORT &= ~(1<<SPI_CS);
  // Start Wiznet W5100 Read OpCode transmission
  SPDR = WIZNET_READ_OPCODE;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address High Bytes transmission
  SPDR = (addr & 0xFF00) >> 8;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address Low Bytes transmission
  SPDR = addr & 0x00FF;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));   

  // Send Dummy transmission for reading the data
  SPDR = 0x00;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));  

  // CS pin is not active
  SPI_PORT |= (1<<SPI_CS);
  return(SPDR);
}
void W5100_Init(void)
{
  // Ethernet Setup
  unsigned char mac_addr[] = {0x00,0x16,0x36,0xDE,0x58,0xF6};
  unsigned char ip_addr[] = {192,168,2,10};
  unsigned char sub_mask[] = {255,255,255,0};
  unsigned char gtw_addr[] = {192,168,2,1};
  // Setting the Wiznet W5100 Mode Register: 0x0000
  SPI_Write(MR,0x80);            // MR = 0b10000000;
  _delay_ms(1);
  printf("Reading MR: %d\n\n",SPI_Read(MR));
  // Setting the Wiznet W5100 Gateway Address (GAR): 0x0001 to 0x0004
  printf("Setting Gateway Address %d.%d.%d.%d\n",gtw_addr[0],gtw_addr[1],\
          gtw_addr[2],gtw_addr[3]);
  SPI_Write(GAR + 0,gtw_addr[0]);
  SPI_Write(GAR + 1,gtw_addr[1]);
  SPI_Write(GAR + 2,gtw_addr[2]);
  SPI_Write(GAR + 3,gtw_addr[3]);
  _delay_ms(1);
  printf("Reading GAR: %d.%d.%d.%d\n\n",SPI_Read(GAR + 0),SPI_Read(GAR + 1),\
          SPI_Read(GAR + 2),SPI_Read(GAR + 3));
  // Setting the Wiznet W5100 Source Address Register (SAR): 0x0009 to 0x000E
  printf("Setting Source Address %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",mac_addr[0],mac_addr[1],\
          mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]);
  SPI_Write(SAR + 0,mac_addr[0]);
  SPI_Write(SAR + 1,mac_addr[1]);
  SPI_Write(SAR + 2,mac_addr[2]);
  SPI_Write(SAR + 3,mac_addr[3]);
  SPI_Write(SAR + 4,mac_addr[4]);
  SPI_Write(SAR + 5,mac_addr[5]);
  _delay_ms(1);
  printf("Reading SAR: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n\n",SPI_Read(SAR + 0),SPI_Read(SAR + 1),\
          SPI_Read(SAR + 2),SPI_Read(SAR + 3),SPI_Read(SAR + 4),SPI_Read(SAR + 5));
  // Setting the Wiznet W5100 Sub Mask Address (SUBR): 0x0005 to 0x0008
  printf("Setting Sub Mask Address %d.%d.%d.%d\n",sub_mask[0],sub_mask[1],\
          sub_mask[2],sub_mask[3]);
  SPI_Write(SUBR + 0,sub_mask[0]);
  SPI_Write(SUBR + 1,sub_mask[1]);
  SPI_Write(SUBR + 2,sub_mask[2]);
  SPI_Write(SUBR + 3,sub_mask[3]);
  _delay_ms(1);
  printf("Reading SUBR: %d.%d.%d.%d\n\n",SPI_Read(SUBR + 0),SPI_Read(SUBR + 1),\
          SPI_Read(SUBR + 2),SPI_Read(SUBR + 3));
  // Setting the Wiznet W5100 IP Address (SIPR): 0x000F to 0x0012
  printf("Setting IP Address %d.%d.%d.%d\n",ip_addr[0],ip_addr[1],\
          ip_addr[2],ip_addr[3]);
  SPI_Write(SIPR + 0,ip_addr[0]);
  SPI_Write(SIPR + 1,ip_addr[1]);
  SPI_Write(SIPR + 2,ip_addr[2]);
  SPI_Write(SIPR + 3,ip_addr[3]);
  _delay_ms(1);
  printf("Reading SIPR: %d.%d.%d.%d\n\n",SPI_Read(SIPR + 0),SPI_Read(SIPR + 1),\
          SPI_Read(SIPR + 2),SPI_Read(SIPR + 3));

  // Setting the Wiznet W5100 RX and TX Memory Size, we use 2KB for Rx/Tx 4 channels
  printf("Setting Wiznet RMSR and TMSR\n\n");
  SPI_Write(RMSR,0x55);
  SPI_Write(TMSR,0x55);
  printf("Done Wiznet W5100 Initialized!\n");
}
// Assign I/O stream to UART
FILE uart_str = FDEV_SETUP_STREAM(uart_putch, uart_getch, _FDEV_SETUP_RW);
int main(void){
  // Set the PORTD as Output:
  DDRD=0xFF;
  PORTD=0x00;
   // Define Output/Input Stream
  stdout = stdin = &uart_str;
  // Initial UART Peripheral
  uart_init();
  // Clear Screen
  ansi_me();
  ansi_cl();
  ansi_me();
  ansi_cl();
  uart_flush();
  // Initial the AVR ATMega168/328 SPI Peripheral
  // Set MOSI (PORTB3),SCK (PORTB5) and PORTB2 (SS) as output, others as input
  SPI_DDR = (1<<PORTB3)|(1<<PORTB5)|(1<<PORTB2);
  // CS pin is not active
  SPI_PORT |= (1<<SPI_CS);
  // Enable SPI, Master Mode 0, set the clock rate fck/2
  SPCR = (1<<SPE)|(1<<MSTR);
  SPSR |= (1<<SPI2X);
  // Initial the Wiznet W5100
  printf("Wiznet W5100 Init\n\n");
  W5100_Init();
  // Loop forever
  for(;;){
  }
  return 0;
}
/* EOF: wiznetping.c */

After compiling and downloading the HEX program into the AVRJazz Mega328 board; connect the RJ45 connector UTP ethernet cable to your hubs/switch or you could connect directly with the cross configuration cable to your computer. Use the serial terminal such as Hyperterminal, puTTY or Tera Term and configure it to accept the serial connection with 19200 baud rate, 8-bit data with No Parity Check.

Now you are ready to test your first embedded Ethernet by using the “ping” command as shown on these following pictures:


Wiznet W5100 SPI Initialization

To initialize the W5100 chip, we need to write on each of the W5100 common registers named MR (Mode Register), SUBR (Subnet mask Register), SAR (Source Hardware Register), SIPR (Source IP Register), RMSR (Receive Memory Size Register) and TMSR (Transmit Memory Size Register).

All the Wiznet W5100 registers address has 16-bits wide and the register it self is 8-bits wide; because we use 8-bit AVR ATMega328 microcontroller SPI, therefore in order to perform write or read operation we need to pass the first 8-bit MSB (most significant byte) and follow by the 8-bit LSB (least significant byte) of the W5100 register address. The Wiznet W5100 also use two operant commands to differentiate between the WRITE (0xF0) and READ (0×0F) operation. The Wiznet W5100 SPI write and read routine is implemented in the SPI_Write() and SPI_Read() functions on the above C code.

The SAR registers is also known as the MAC (Media Access Control) address, this W5100 register will represent the unique hardware identification in the network. The MAC address is assigned and managed by Institute of Electrical and Electronics Engineers (IEEE) for each NIC (Network Interface Card) manufacturer where the first 3 bytes of 6 bytes MAC address is used to identify the organization that issued the identifier and are known as the OUI (Organizationally Unique Identifier). For example the following are the list of Atmel, Microchip and Wiznet OUI:

00-04-25   (hex)      Atmel Corporation
000425     (base 16)  Atmel Corporation
                      Multimedia & Communications Group
                      2200 Gateway Centre, Suite 201
                      Morrisville NC 27560
                      UNITED STATES
00-04-A3   (hex)      Microchip Technology, Inc.
0004A3     (base 16)  Microchip Technology, Inc.
                      2355 W. Chandler Blvd.
                      Chandler AZ 85224
                      UNITED STATES
00-08-DC   (hex)      Wiznet
0008DC     (base 16)  Wiznet
                      5F Simmtech bldg., 228-3,
                      Nonyhun, Kangnam
                      Seoul  135-830
                      KOREA, REPUBLIC OF

Now the question is how you could get your own MAC address because the Wiznet W5100 chip is shipped without its own MAC address; the answer is you could either register your own MAC address (OUI) to IEEE (of course this is not recommended for the hobbyist as this will be very expensive unless you could effort it) or you could simply use your own computer NIC’s MAC address and just change the last byte of the 6 byte MAC address and cross you finger, hopping this MAC address will be unique within your network as I did. By using the “ipconfig /all” command in the window command prompt, you could get the information of your computer MAC address in hex notation i.e. 00-16-36-DE-58-F5 (the MAC address of my computer); now by adding one to the last byte you will get the W5100 MAC address that I used in this tutorial (00-16-36-DE-58-F6).

The Wiznet W5100 support up to four simultaneous channels or sockets, each of the channels has its own registers address to control the operation. All of these channels is supported by 8 KB memory of the transmit buffer and 8 KB memory of the receive buffer. We could adjust this memory size on each channel by assigning the RMSR and TMSR register (the default is 2 KB for each channel) as shown on this following C code:

  // Setting the Wiznet W5100 RX and TX Memory Size, we use 2KB for Rx/Tx 4 channels
  printf("Setting Wiznet RMSR and TMSR\n\n");
  SPI_Write(RMSR,0x55);
  SPI_Write(TMSR,0x55);

All the W5100 initialization routine is implemented in W5100_Init() function, where we perform both writing and reading in order to understand how we could control the W5100 register through the SPI. The following is the summary of how we initialized the W5100 register. For the complete information about the W5100 registers please refer to the Wiznet W5100 datasheet (consider it as your best friend in this tutorial):

  • Write 0×80 to W5100 MR (Mode Register) on address: 0×0000 to soft reset the chip
  • Assign four bytes of the gateway IP address to the W5100 GAR (Gateway Address Register) on address 0×0001 to 0×0004
  • Assign four bytes of the sub mask address to the W5100 SUBR (Sub Mask Address Register) on address 0×0005 to 0×0008
  • Assign six bytes of the MAC address to the W5100 SAR (Source Address Register) on address 0×0009 to 0×000E
  • Assign four bytes of the IP address to the W5100 SIPR (Source IP Register) on address 0×000F to 0×0012
  • The last is to allocate the transmit and receive buffer size to each of RMSR (RX Memory Size Register) address: 0×001A and TMSR (TX Memory Size Register) on address 0×001B.
  • Because we use the default value 0×07D0 (200 ms, where 1 mean 100 us) of the RTR (Retry Time Value Register) and default value 0×08 (8 times retry before generating interrupt) of the RCR (Retry Count Register), therefore we don’t need to set these registers

After performing all the W5100 required initialization routine you could examine whether it work or not by sending the “ping” command to the W5100 build in ICMP (Internet Control Message Protocol) package responder. If everything works, then you will get the ICMP reply from the Wiznet W5100 chip. With this understanding now we are ready to continue this tutorial and make our first simple embedded web server.

The Embedded Web Server

The advantage of using the Hypertext Transfer Protocol (HTTP) server in the embedded system is; you don’t have to develop a special client application to communicate with your embedded system. All you need is to use any standard browser that comes with your personal computer operating system or gadget to talk to your embedded system. The HTTP server uses a simple text called Hypertext Markup Language (HTML) to interact with the browser (client application) through the TCP/IP protocol.

The HTTP server work by listening to any request from the client (browser) for any HTTP “GET” or “POST” request through the TCP/IP port 80 (standard HTTP server port). Once the client sends this request to the HTTP server, then the HTTP server will response to this client request by sending the HTTP response header (HTTP/1.0 200 OK and Content-Type: text/html) follow by the blank line and the HTML text to the client, after transfer all the HTML text to the client; the HTTP server will automatically disconnect the established connection with the client. The following is the example of the client request and the HTML text response transmitted by the embedded HTTP server:

Client Request:

GET / HTTP/1.1
Host: 192.168.2.101
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20
100401 Firefox/3.6.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive

HTTP Server Response:

HTTP/1.0 200 OK
Content-Type: text/html
<html>
<body>
<span style="color:#0000A0">
<h1>Embedded Web Server</h1>
<h3>AVRJazz Mega328 and WIZ811MJ</h3>
<p><form method="POST">
<strong>Temp: <input type="text" size=2 value="26"> <sup>O</sup>C
<p><input type="radio" name="radio" value="0" >Blinking LED
<br><input type="radio" name="radio" value="1" checked>Scanning LED
</strong><p>
<input type="submit">
</form></span>
</body>
</html>

The client then will translate this received HTML text and display the information on the browser screen such as room’s temperature and the output LED status. By submitting different LED setting from the browser (POST request) to the HTTP server, now we could easily give the needed instruction to the AVR ATMega328 microcontroller that also functioned as the embedded web server.

Now as you understand the basic principal of how the HTTP server protocol work, its time to implement it on the AVR ATMega328 Microcontroller and Wiznet W5100 chip. The following is the complete C code called “wiznetweb.c” for our embedded web server:

/*****************************************************************************
//  File Name    : wiznetweb.c
//  Version      : 1.0
//  Description  : AVRJazz Mega328 and Wiznet W5100 Web Server
//  Author       : RWB
//  Target       : AVRJazz Mega328 Board
//  Compiler     : AVR-GCC 4.3.2; avr-libc 1.6.6 (WinAVR 20090313)
//  IDE          : Atmel AVR Studio 4.17
//  Programmer   : AVRJazz Mega328 STK500 v2.0 Bootloader
//               : AVR Visual Studio 4.17, STK500 programmer
//  Last Updated : 20 July 2010
*****************************************************************************/
#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
// AVRJazz Mega328 SPI I/O
#define SPI_PORT PORTB
#define SPI_DDR  DDRB
#define SPI_CS   PORTB2
// Wiznet W5100 Op Code
#define WIZNET_WRITE_OPCODE 0xF0
#define WIZNET_READ_OPCODE 0x0F
// Wiznet W5100 Register Addresses
#define MR         0x0000      // Mode Register
#define GAR        0x0001      // Gateway Address: 0x0001 to 0x0004
#define SUBR       0x0005      // Subnet mask Address: 0x0005 to 0x0008
#define SAR        0x0009      // Source Hardware Address (MAC): 0x0009 to 0x000E
#define SIPR       0x000F      // Source IP Address: 0x000F to 0x0012
#define RMSR       0x001A      // RX Memory Size Register
#define TMSR       0x001B      // TX Memory Size Register
#define S0_MR	   0x0400      // Socket 0: Mode Register Address
#define S0_CR	   0x0401      // Socket 0: Command Register Address
#define S0_IR	   0x0402      // Socket 0: Interrupt Register Address
#define S0_SR	   0x0403      // Socket 0: Status Register Address
#define S0_PORT    0x0404      // Socket 0: Source Port: 0x0404 to 0x0405
#define SO_TX_FSR  0x0420      // Socket 0: Tx Free Size Register: 0x0420 to 0x0421
#define S0_TX_RD   0x0422      // Socket 0: Tx Read Pointer Register: 0x0422 to 0x0423
#define S0_TX_WR   0x0424      // Socket 0: Tx Write Pointer Register: 0x0424 to 0x0425
#define S0_RX_RSR  0x0426      // Socket 0: Rx Received Size Pointer Register: 0x0425 to 0x0427
#define S0_RX_RD   0x0428      // Socket 0: Rx Read Pointer: 0x0428 to 0x0429
#define TXBUFADDR  0x4000      // W5100 Send Buffer Base Address
#define RXBUFADDR  0x6000      // W5100 Read Buffer Base Address
// S0_MR values
#define MR_CLOSE	  0x00    // Unused socket
#define MR_TCP		  0x01    // TCP
#define MR_UDP		  0x02    // UDP
#define MR_IPRAW	  0x03	  // IP LAYER RAW SOCK
#define MR_MACRAW	  0x04	  // MAC LAYER RAW SOCK
#define MR_PPPOE	  0x05	  // PPPoE
#define MR_ND		  0x20	  // No Delayed Ack(TCP) flag
#define MR_MULTI	  0x80	  // support multicating
// S0_CR values
#define CR_OPEN          0x01	  // Initialize or open socket
#define CR_LISTEN        0x02	  // Wait connection request in tcp mode(Server mode)
#define CR_CONNECT       0x04	  // Send connection request in tcp mode(Client mode)
#define CR_DISCON        0x08	  // Send closing reqeuset in tcp mode
#define CR_CLOSE         0x10	  // Close socket
#define CR_SEND          0x20	  // Update Tx memory pointer and send data
#define CR_SEND_MAC      0x21	  // Send data with MAC address, so without ARP process
#define CR_SEND_KEEP     0x22	  // Send keep alive message
#define CR_RECV          0x40	  // Update Rx memory buffer pointer and receive data
// S0_SR values
#define SOCK_CLOSED      0x00     // Closed
#define SOCK_INIT        0x13	  // Init state
#define SOCK_LISTEN      0x14	  // Listen state
#define SOCK_SYNSENT     0x15	  // Connection state
#define SOCK_SYNRECV     0x16	  // Connection state
#define SOCK_ESTABLISHED 0x17	  // Success to connect
#define SOCK_FIN_WAIT    0x18	  // Closing state
#define SOCK_CLOSING     0x1A	  // Closing state
#define SOCK_TIME_WAIT	 0x1B	  // Closing state
#define SOCK_CLOSE_WAIT  0x1C	  // Closing state
#define SOCK_LAST_ACK    0x1D	  // Closing state
#define SOCK_UDP         0x22	  // UDP socket
#define SOCK_IPRAW       0x32	  // IP raw mode socket
#define SOCK_MACRAW      0x42	  // MAC raw mode socket
#define SOCK_PPPOE       0x5F	  // PPPOE socket
#define TX_BUF_MASK      0x07FF   // Tx 2K Buffer Mask:
#define RX_BUF_MASK      0x07FF   // Rx 2K Buffer Mask:
#define NET_MEMALLOC     0x05     // Use 2K of Tx/Rx Buffer
#define TCP_PORT         80       // TCP/IP Port
// Debugging Mode, 0 - Debug OFF, 1 - Debug ON
#define _DEBUG_MODE      0
#if _DEBUG_MODE
  #define BAUD_RATE 19200
#endif
// Define W5100 Socket Register and Variables Used
uint8_t sockreg;
#define MAX_BUF 512
uint8_t buf[MAX_BUF];
int tempvalue;
uint8_t ledmode,ledeye,ledsign;
#if _DEBUG_MODE
void uart_init(void)
{
  UBRR0H = (((F_CPU/BAUD_RATE)/16)-1)>>8;		// set baud rate
  UBRR0L = (((F_CPU/BAUD_RATE)/16)-1);
  UCSR0B = (1<<RXEN0)|(1<<TXEN0); 				// enable Rx & Tx
  UCSR0C=  (1<<UCSZ01)|(1<<UCSZ00);  	        // config USART; 8N1
}
void uart_flush(void)
{
  unsigned char dummy;
  while (UCSR0A & (1<<RXC0)) dummy = UDR0;
}
int uart_putch(char ch,FILE *stream)
{
   if (ch == '\n')
    uart_putch('\r', stream);
   while (!(UCSR0A & (1<<UDRE0)));
   UDR0=ch;
   return 0;
}
int uart_getch(FILE *stream)
{
   unsigned char ch;
   while (!(UCSR0A & (1<<RXC0)));
   ch=UDR0;  

   /* Echo the Output Back to terminal */
   uart_putch(ch,stream);       

   return ch;
}
void ansi_cl(void)
{
  // ANSI clear screen: cl=\E[H\E[J
  putchar(27);
  putchar('[');
  putchar('H');
  putchar(27);
  putchar('[');
  putchar('J');
}
void ansi_me(void)
{
  // ANSI turn off all attribute: me=\E[0m
  putchar(27);
  putchar('[');
  putchar('0');
  putchar('m');
}
#endif
void SPI_Write(uint16_t addr,uint8_t data)
{
  // Activate the CS pin
  SPI_PORT &= ~(1<<SPI_CS);
  // Start Wiznet W5100 Write OpCode transmission
  SPDR = WIZNET_WRITE_OPCODE;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address High Bytes transmission
  SPDR = (addr & 0xFF00) >> 8;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address Low Bytes transmission
  SPDR = addr & 0x00FF;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));   

  // Start Data transmission
  SPDR = data;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // CS pin is not active
  SPI_PORT |= (1<<SPI_CS);
}
unsigned char SPI_Read(uint16_t addr)
{
  // Activate the CS pin
  SPI_PORT &= ~(1<<SPI_CS);
  // Start Wiznet W5100 Read OpCode transmission
  SPDR = WIZNET_READ_OPCODE;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address High Bytes transmission
  SPDR = (addr & 0xFF00) >> 8;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));
  // Start Wiznet W5100 Address Low Bytes transmission
  SPDR = addr & 0x00FF;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));   

  // Send Dummy transmission for reading the data
  SPDR = 0x00;
  // Wait for transmission complete
  while(!(SPSR & (1<<SPIF)));  

  // CS pin is not active
  SPI_PORT |= (1<<SPI_CS);
  return(SPDR);
}
void W5100_Init(void)
{
  // Ethernet Setup
  unsigned char mac_addr[] = {0x00,0x16,0x36,0xDE,0x58,0xF6};
  unsigned char ip_addr[] = {192,168,2,10};
  unsigned char sub_mask[] = {255,255,255,0};
  unsigned char gtw_addr[] = {192,168,2,1};
  // Setting the Wiznet W5100 Mode Register: 0x0000
  SPI_Write(MR,0x80);            // MR = 0b10000000;
  // Setting the Wiznet W5100 Gateway Address (GAR): 0x0001 to 0x0004
  SPI_Write(GAR + 0,gtw_addr[0]);
  SPI_Write(GAR + 1,gtw_addr[1]);
  SPI_Write(GAR + 2,gtw_addr[2]);
  SPI_Write(GAR + 3,gtw_addr[3]);
  // Setting the Wiznet W5100 Source Address Register (SAR): 0x0009 to 0x000E
  SPI_Write(SAR + 0,mac_addr[0]);
  SPI_Write(SAR + 1,mac_addr[1]);
  SPI_Write(SAR + 2,mac_addr[2]);
  SPI_Write(SAR + 3,mac_addr[3]);
  SPI_Write(SAR + 4,mac_addr[4]);
  SPI_Write(SAR + 5,mac_addr[5]);
  // Setting the Wiznet W5100 Sub Mask Address (SUBR): 0x0005 to 0x0008
  SPI_Write(SUBR + 0,sub_mask[0]);
  SPI_Write(SUBR + 1,sub_mask[1]);
  SPI_Write(SUBR + 2,sub_mask[2]);
  SPI_Write(SUBR + 3,sub_mask[3]);
  // Setting the Wiznet W5100 IP Address (SIPR): 0x000F to 0x0012
  SPI_Write(SIPR + 0,ip_addr[0]);
  SPI_Write(SIPR + 1,ip_addr[1]);
  SPI_Write(SIPR + 2,ip_addr[2]);
  SPI_Write(SIPR + 3,ip_addr[3]);    

  // Setting the Wiznet W5100 RX and TX Memory Size (2KB),
  SPI_Write(RMSR,NET_MEMALLOC);
  SPI_Write(TMSR,NET_MEMALLOC);
}
void close(uint8_t sock)
{
   if (sock != 0) return;

   // Send Close Command
   SPI_Write(S0_CR,CR_CLOSE);
   // Waiting until the S0_CR is clear
   while(SPI_Read(S0_CR));
}
void disconnect(uint8_t sock)
{
   if (sock != 0) return;
   // Send Disconnect Command
   SPI_Write(S0_CR,CR_DISCON);
   // Wait for Disconecting Process
   while(SPI_Read(S0_CR));
}
uint8_t socket(uint8_t sock,uint8_t eth_protocol,uint16_t tcp_port)
{
    uint8_t retval=0;
    if (sock != 0) return retval;

    // Make sure we close the socket first
    if (SPI_Read(S0_SR) == SOCK_CLOSED) {
      close(sock);
    }
    // Assigned Socket 0 Mode Register
    SPI_Write(S0_MR,eth_protocol);

    // Now open the Socket 0
    SPI_Write(S0_PORT,((tcp_port & 0xFF00) >> 8 ));
    SPI_Write(S0_PORT + 1,(tcp_port & 0x00FF));
    SPI_Write(S0_CR,CR_OPEN);                   // Open Socket
    // Wait for Opening Process
    while(SPI_Read(S0_CR));
    // Check for Init Status
    if (SPI_Read(S0_SR) == SOCK_INIT)
      retval=1;
    else
      close(sock);

    return retval;
}
uint8_t listen(uint8_t sock)
{
   uint8_t retval = 0;
   if (sock != 0) return retval;
   if (SPI_Read(S0_SR) == SOCK_INIT) {
     // Send the LISTEN Command
     SPI_Write(S0_CR,CR_LISTEN);

     // Wait for Listening Process
     while(SPI_Read(S0_CR));
     // Check for Listen Status
     if (SPI_Read(S0_SR) == SOCK_LISTEN)
       retval=1;
     else
       close(sock);
    }
    return retval;
}
uint16_t send(uint8_t sock,const uint8_t *buf,uint16_t buflen)
{
    uint16_t ptr,offaddr,realaddr,txsize,timeout;   

    if (buflen <= 0 || sock != 0) return 0;
#if _DEBUG_MODE
    printf("Send Size: %d\n",buflen);
#endif
    // Make sure the TX Free Size Register is available
    txsize=SPI_Read(SO_TX_FSR);
    txsize=(((txsize & 0x00FF) << 8 ) + SPI_Read(SO_TX_FSR + 1));
#if _DEBUG_MODE
    printf("TX Free Size: %d\n",txsize);
#endif
    timeout=0;
    while (txsize < buflen) {
      _delay_ms(1);
     txsize=SPI_Read(SO_TX_FSR);
     txsize=(((txsize & 0x00FF) << 8 ) + SPI_Read(SO_TX_FSR + 1));
     // Timeout for approx 1000 ms
     if (timeout++ > 1000) {
#if _DEBUG_MODE
       printf("TX Free Size Error!\n");
#endif
       // Disconnect the connection
       disconnect(sock);
       return 0;
     }
   }	

   // Read the Tx Write Pointer
   ptr = SPI_Read(S0_TX_WR);
   offaddr = (((ptr & 0x00FF) << 8 ) + SPI_Read(S0_TX_WR + 1));
#if _DEBUG_MODE
    printf("TX Buffer: %x\n",offaddr);
#endif	

    while(buflen) {
      buflen--;
      // Calculate the real W5100 physical Tx Buffer Address
      realaddr = TXBUFADDR + (offaddr & TX_BUF_MASK);
      // Copy the application data to the W5100 Tx Buffer
      SPI_Write(realaddr,*buf);
      offaddr++;
      buf++;
    }

    // Increase the S0_TX_WR value, so it point to the next transmit
    SPI_Write(S0_TX_WR,(offaddr & 0xFF00) >> 8 );
    SPI_Write(S0_TX_WR + 1,(offaddr & 0x00FF));	

    // Now Send the SEND command
    SPI_Write(S0_CR,CR_SEND);

    // Wait for Sending Process
    while(SPI_Read(S0_CR));	

    return 1;
}
uint16_t recv(uint8_t sock,uint8_t *buf,uint16_t buflen)
{
    uint16_t ptr,offaddr,realaddr;   	

    if (buflen <= 0 || sock != 0) return 1;   

    // If the request size > MAX_BUF,just truncate it
    if (buflen > MAX_BUF)
      buflen=MAX_BUF - 2;
    // Read the Rx Read Pointer
    ptr = SPI_Read(S0_RX_RD);
    offaddr = (((ptr & 0x00FF) << 8 ) + SPI_Read(S0_RX_RD + 1));
#if _DEBUG_MODE
    printf("RX Buffer: %x\n",offaddr);
#endif	

    while(buflen) {
      buflen--;
      realaddr=RXBUFADDR + (offaddr & RX_BUF_MASK);
      *buf = SPI_Read(realaddr);
      offaddr++;
      buf++;
    }
    *buf='\0';        // String terminated character

    // Increase the S0_RX_RD value, so it point to the next receive
    SPI_Write(S0_RX_RD,(offaddr & 0xFF00) >> 8 );
    SPI_Write(S0_RX_RD + 1,(offaddr & 0x00FF));	

    // Now Send the RECV command
    SPI_Write(S0_CR,CR_RECV);
    _delay_us(5);    // Wait for Receive Process

    return 1;
}
uint16_t recv_size(void)
{
  return ((SPI_Read(S0_RX_RSR) & 0x00FF) << 8 ) + SPI_Read(S0_RX_RSR + 1);
}
int strindex(char *s,char *t)
{
  uint16_t i,n;

  n=strlen(t);
  for(i=0;*(s+i); i++) {
    if (strncmp(s+i,t,n) == 0)
      return i;
  }
  return -1;
}
ISR(TIMER0_OVF_vect)
{
  static unsigned char tenms=1;
  tenms++;                  // Read ADC every 20 x 10ms = 200 milisecond
  if (tenms >= 20) {
    cli();                                // Disable Interupt
    // Select the LED Mode here
    if (ledmode == 1) {
      if (ledeye <= 0) ledeye=0x01;
      if (ledsign == 0) {
	PORTD=ledeye;
	ledeye=ledeye << 1;
	if (ledeye >= 0x80) ledsign=1;
      } else {
	PORTD=ledeye;
	ledeye=ledeye >> 1;
	if (ledeye <= 0x01) ledsign=0;
      }
    } else {
      if (ledsign == 0) {
	PORTD=0x00;
	ledsign=1;
      } else {
	PORTD=0xFF;
	ledsign=0;
      }
    }
    // Set ADMUX Channel for LM35DZ Input
    ADMUX=0x01;
    // Start conversion by setting ADSC on ADCSRA Register
    ADCSRA |= (1<<ADSC);
    // wait until convertion complete ADSC=0 -> Complete
    while (ADCSRA & (1<<ADSC));
    // Get the ADC Result
    tempvalue = ADCW;
    // ADC = (Vin x 1024) / Vref, Vref = 1 Volt, LM35DZ Out = 10mv/C
    tempvalue = (int)(tempvalue) / 10.24;

    tenms=1;	 

    sei();                            // Enable Interupt
  }
  // Start counter from 0x94, overflow at 10 mSec
  TCNT0=0x94;
}
#if _DEBUG_MODE
// Assign I/O stream to UART
FILE uart_str = FDEV_SETUP_STREAM(uart_putch, uart_getch, _FDEV_SETUP_RW);
#endif
int main(void){
  uint8_t sockstat;
  uint16_t rsize;
  char radiostat0[10],radiostat1[10],temp[4];
  int getidx,postidx;

  // Reset Port D
  DDRD = 0xFF;       // Set PORTD as Output
  PORTD = 0x00;	     

#if _DEBUG_MODE
  // Define Output/Input Stream
  stdout = stdin = &uart_str;
  // Initial UART Peripheral
  uart_init();
  // Clear Screen
  ansi_me();
  ansi_cl();
  ansi_me();
  ansi_cl();
  uart_flush();
#endif
  // Initial ATMega386 ADC Peripheral
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);
  // Free running ADC Mode
  ADCSRB = 0x00;
  // Initial the AVR ATMega328 SPI Peripheral
  // Set MOSI (PORTB3),SCK (PORTB5) and PORTB2 (SS) as output, others as input
  SPI_DDR = (1<<PORTB3)|(1<<PORTB5)|(1<<PORTB2);
  // CS pin is not active
  SPI_PORT |= (1<<SPI_CS);
  // Enable SPI, Master Mode 0, set the clock rate fck/2
  SPCR = (1<<SPE)|(1<<MSTR);
  SPSR |= (1<<SPI2X);  

  // Initial ATMega368 Timer/Counter0 Peripheral
  TCCR0A=0x00;                  // Normal Timer0 Operation
  TCCR0B=(1<<CS02)|(1<<CS00);   // Use maximum prescaller: Clk/1024
  TCNT0=0x94;                   // Start counter from 0x94, overflow at 10 mSec
  TIMSK0=(1<<TOIE0);            // Enable Counter Overflow Interrupt
  sei();                        // Enable Interrupt

  // Initial the W5100 Ethernet
  W5100_Init();
  // Initial variable used
  sockreg=0;
  tempvalue=0;
  ledmode=1;
  ledeye=0x01;                  // Initial LED Eye Variables
  ledsign=0;
#if _DEBUG_MODE
  printf("WEB Server Debug Mode\n\n");
#endif

  // Loop forever
  for(;;){
    sockstat=SPI_Read(S0_SR);
    switch(sockstat) {
     case SOCK_CLOSED:
        if (socket(sockreg,MR_TCP,TCP_PORT) > 0) {
	  // Listen to Socket 0
	  if (listen(sockreg) <= 0)
	    _delay_ms(1);
#if _DEBUG_MODE
          printf("Socket Listen!\n");
#endif
	}
	break;
     case SOCK_ESTABLISHED:
	// Get the client request size
        rsize=recv_size();
#if _DEBUG_MODE
	printf("Size: %d\n",rsize);
#endif
	if (rsize > 0) {
	  // Now read the client Request
	  if (recv(sockreg,buf,rsize) <= 0) break;
#if _DEBUG_MODE
  	  printf("Content:\n%s\n",buf);
#endif
          // Check the Request Header
	  getidx=strindex((char *)buf,"GET /");
	  postidx=strindex((char *)buf,"POST /");
	  if (getidx >= 0 || postidx >= 0) {
#if _DEBUG_MODE
	    printf("Req. Check!\n");
#endif
            // Now check the Radio Button for POST request
	    if (postidx >= 0) {
	      if (strindex((char *)buf,"radio=0") > 0)
	        ledmode=0;
	      if (strindex((char *)buf,"radio=1") > 0)
	        ledmode=1;
            }
#if _DEBUG_MODE
	    printf("Req. Send!\n");
#endif
	    // Create the HTTP Response	Header
	    strcpy_P((char *)buf,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"));
	    strcat_P((char *)buf,PSTR("<html><body><span style=\"color:#0000A0\">\r\n"));
	    strcat_P((char *)buf,PSTR("<h1>Embedded Web Server</h1>\r\n"));
	    strcat_P((char *)buf,PSTR("<h3>AVRJazz Mega328 and WIZ811MJ</h3>\r\n"));
	    strcat_P((char *)buf,PSTR("<p><form method=\"POST\">\r\n"));
	    // Now Send the HTTP Response
	    if (send(sockreg,buf,strlen((char *)buf)) <= 0) break;

	    // Create the HTTP Temperature Response
	    sprintf((char *)temp,"%d",tempvalue);        // Convert temperature value to string
	    strcpy_P((char *)buf,PSTR("<strong>Temp: <input type=\"text\" size=2 value=\""));
	    strcat((char *)buf,temp);
	    strcat_P((char *)buf,PSTR("\"> <sup>O</sup>C\r\n"));									

	    if (ledmode == 1) {
	      strcpy(radiostat0,"");
	      strcpy_P(radiostat1,PSTR("checked"));
	    } else {
	      strcpy_P(radiostat0,PSTR("checked"));
	      strcpy(radiostat1,"");
	    }
            // Create the HTTP Radio Button 0 Response
	    strcat_P((char *)buf,PSTR("<p><input type=\"radio\" name=\"radio\" value=\"0\" "));
	    strcat((char *)buf,radiostat0);
	    strcat_P((char *)buf,PSTR(">Blinking LED\r\n"));
	    strcat_P((char *)buf,PSTR("<br><input type=\"radio\" name=\"radio\" value=\"1\" "));
	    strcat((char *)buf,radiostat1);
	    strcat_P((char *)buf,PSTR(">Scanning LED\r\n"));
 	    strcat_P((char *)buf,PSTR("</strong><p>\r\n"));
	    strcat_P((char *)buf,PSTR("<input type=\"submit\">\r\n"));
	    strcat_P((char *)buf,PSTR("</form></span></body></html>\r\n"));
            // Now Send the HTTP Remaining Response
	    if (send(sockreg,buf,strlen((char *)buf)) <= 0) break;
          }
	  // Disconnect the socket
	  disconnect(sockreg);
        } else
	  _delay_us(10);    // Wait for request
	break;
      case SOCK_FIN_WAIT:
      case SOCK_CLOSING:
      case SOCK_TIME_WAIT:
      case SOCK_CLOSE_WAIT:
      case SOCK_LAST_ACK:
        // Force to close the socket
	close(sockreg);
#if _DEBUG_MODE
	printf("Socket Close!\n");
#endif
	break;
    }
  }
  return 0;
}
/* EOF: wiznetweb.c */

From the C code above you’ve noticed that I used many of the Atmel AVR ATMega328 microcontroller build in peripherals features such as SPI, ADC, TIMER and the UART at the same time in order to achieve the project’s goal, you could read more information of how to use all of these peripherals on my previous posted blog Analog to Digital Converter AVR C Programming and Working with AVR microcontroller Communication Port Project.

The W5100 HTTP Server

To build the HTTP server we need to listen for the client request, read the client request and send the HTML response to the client through the TCP/IP protocol. To configure and control the Wiznet W5100 as the HTTP server basically we need to write and read to and from the Wiznet W5100 Socket Control Register and the TX and RX buffer memory; for the purpose of this tutorial (easier to understand) I only implement one channel (socket 0) of the four available channels (socket 0, 1, 2 and 3) supported by the W5100 chip. But once you understand the basic principal of how the W5100 work on this tutorial it would be easy to implement the remaining channels.

The Wiznet W5100 socket 0 control registers start at address 0×0400 to 0×04FF, the following is the list of W5100 socket 0 control registers with the 2 KB of TX and RX memory buffer used to make the HTTP server in this tutorial (again please refer to the W5100 datasheet for detail information).

Now fasten your seat belt as we are going through the code algorithm behind this W5100 HTTP server program!

Basically we use the command register (S0_CR) to instruct the W5100 chip to write or read to or from the W5100 memory buffer to make a simple HTTP server that response to the client request. The following diagram show of how we could achieve this objective:

From the diagram above after we initialized the W5100 (i.e. it could response to the “ping” command request) we continue with opening the TCP/IP port 80 and listen to this port for any client request. Assigning the TCP/IP protocol and opening the port 80 routine is implemented in the socket() function as shown on this following C code:

...
// Assigned Socket 0 Mode Register
SPI_Write(S0_MR,eth_protocol);

// Now open the Socket 0
SPI_Write(S0_PORT,((tcp_port & 0xFF00) >> 8 ));
SPI_Write(S0_PORT + 1,(tcp_port & 0x00FF));
SPI_Write(S0_CR,CR_OPEN);                   // Open Socket
// Wait for Opening Process
while(SPI_Read(S0_CR));
...

After we wrote the CR_OPEN (0×01) command on the socket 0 command register (S0_CR), then the W5100 will automatically clear this register; therefore we could take advantage of this behavior by reading this register again and examine its contents. Upon completion of the command execution process the contents of the SO_CR register will be reset to 0×00 and the socket 0 status register (S0_SR) will be set to the SOCK_INIT (0×13) status.

Listening to the opening TCP/IP port (i.e. 80) could be done by sending the CR_LISTEN (0×02) command to the socket 0 command register (S0_CR), this will change the socket 0 status register (S0_SR) to the SOCK_LISTEN (0×14) status. The listening routine is implemented in the listen() function. Now the Wiznet W5100 is ready to receive the request from client (browser).

When the client established the connection with the HTTP server, it will send the standard HTTP request protocol to the HTTP server. We could examine whether the connection has been established with the client by reading the status register (SO_CR) for SOCK_ESTABLISHED (0×17) status. Next we examine the RX receive data size by reading the socket 0 received size register (SO_RX_RSR); this routine is implemented in recv_size() function bellow:

uint16_t recv_size(void)
{
  return ((SPI_Read(S0_RX_RSR) & 0x00FF) << 8 ) + SPI_Read(S0_RX_RSR + 1);
}

If the received data exist in the RX buffer memory then we continue to read the RX memory buffer content which is implemented in recv() function. The reading process is accomplished by first calculating the received data physical address location at the 2 KB (2048 or 0×800 in hex notations) RX memory buffer boundary and then start to read the data from this location. The following picture show of how we determine the physical address of the received data.

To determine the Wiznet W5100 RX memory buffer physical address where we start to read the received data we have to mask the value returned by the socket 0 RX read pointer (S0_RX_RD) with the 0×7FF (2 KB minus one in hex notations) and add the result to the RX memory buffer base address 0×6000. Next we use this address result to retrieve the data from RX memory buffer as shown on this following C code:

...
// Read the Rx Read Pointer
ptr = SPI_Read(S0_RX_RD);
offaddr = (((ptr & 0x00FF) << 8 ) + SPI_Read(S0_RX_RD + 1));

while(buflen) {
  buflen--;
  realaddr=RXBUFADDR + (offaddr & RX_BUF_MASK);
  *buf = SPI_Read(realaddr);
  offaddr++;
  buf++;
}
*buf='\0';        // String terminated character
...

After reading all the data we need to write the last pointer value back to the socket 0 RX read pointer register (S0_RX_RD) so the W5100 RX received circular buffer mechanism will know where to put the next received data on the RX memory buffer as shown on this following C code:

...
// Increase the S0_RX_RD value, so it point to the next receive
SPI_Write(S0_RX_RD,(offaddr & 0xFF00) >> 8 );
SPI_Write(S0_RX_RD + 1,(offaddr & 0x00FF));
...

Finally we give the CR_RECV (0×40) to the W5100 socket 0 command register (S0_CR) to complete the W5100 receiving process (updating the RX memory buffer pointer and received data).

...
// Now Send the RECV command
SPI_Write(S0_CR,CR_RECV);
_delay_us(5);    // Wait for Receive Process
...

The HTTP server will send the response after examining the client request; on this tutorial the response will be the room’s temperature read by the AVR ATMega328 microcontroller ADC peripheral through the AVRJazz Mega328 on board LM35DZ temperature sensor and setting the LED display. In order to save the microcontroller RAM; all the static HTML data is stored in the flash program memory (flash RAM) use a special AVR-GCC PSTR macro and string manipulation functions such as strcpy_P() and strcat_P() to work with static string data stored in the flash program memory as shown on this following C code:

...
// Create the HTTP Response Header
strcpy_P((char *)buf,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"));
strcat_P((char *)buf,PSTR("<html><body><span style=\"color:#0000A0\">\r\n"));
strcat_P((char *)buf,PSTR("<h1>Embedded Web Server</h1>\r\n"));
...

After copying all the HTML response to the application buffer data (buf), now we are ready to send the response to the client. This function is implemented in the send() function. Before sending we need to check the availability of the W5100 TX memory buffer by reading the W5100 socket 0 free size register (S0_TX_FSR); on normal condition this register should return the value of 2 KB (0×0200) free. After examining this register we are ready to send data in the application buffer and use the same principal as receiving data to determine the actual TX buffer physical address as shown on this following diagram:

Similar to the read operation; to determine the Wiznet W5100 TX memory buffer physical address where we start to write the data we have to mask the value returned by the socket 0 TX write pointer (S0_TX_WR) with the 0×7FF (2 KB minus one in hex notations) and add the result to the TX memory buffer base address 0×4000. Next we use this address result to put the data to TX memory buffer as shown on this following C code:

...
// Read the Tx Write Pointer
ptr = SPI_Read(S0_TX_WR);
offaddr = (((ptr & 0x00FF) << 8 ) + SPI_Read(S0_TX_WR + 1));

while(buflen) {
  buflen--;
  // Calculate the real W5100 physical Tx Buffer Address
  realaddr = TXBUFADDR + (offaddr & TX_BUF_MASK);
  // Copy the application data to the W5100 Tx Buffer
  SPI_Write(realaddr,*buf);
  offaddr++;
  buf++;
}
...

After putting all the data to the TX memory buffer, we need to write the last pointer value back to the socket 0 TX write pointer register (S0_TX_WR) for the next TX memory buffer writing as shown on this following C code:

...
// Increase the S0_TX_WR value, so it point to the next transmit
SPI_Write(S0_TX_WR,(offaddr & 0xFF00) >> 8 );
SPI_Write(S0_TX_WR + 1,(offaddr & 0x00FF));
...

Next we need to write the CR_SEND (0×20) command to the W5100 socket 0 command register (S0_CR) to instruct the W5100 chip to send the HTML response data in the TX memory buffer.

...
// Now Send the SEND command
SPI_Write(S0_CR,CR_SEND);
...

To comply with the HTTP protocol requirement, after sending the HTML response to the client we need to disconnect and close the connection with the client. These routines are implemented in the disconnect() and close() functionsThese two functions simply send the CR_DISCON (0×08) and CR_CLOSE (0×10) to the W5100 socket 0 command register (S0_CR) respectively as shown on this following C code:

...
// Send Disconnect Command
SPI_Write(S0_CR,CR_DISCON);
...
...
// Send Close Command
SPI_Write(S0_CR,CR_CLOSE);
...

Next the infinite loop routine in main program will start opening and listening to the new client request and the whole process is repeated again.

The Temperature and LED Display Indicator

Before executing the for() infinite loop, first we initialized all the needed peripherals such as PORTD for displaying LED, UART for debugging, ADC for reading temperature sensor, SPI for communicating with the Wiznet W5100 chip and last TIMER0 for ADC conversion and controlling the LED display.

The HTTP server protocol handshaking is implemented inside the infinite loop and I use the AVR ATMega328 microcontroller TIMER0 to implement the temperature reading through the ADC peripheral and to control the LED display indicator. The AVR ATMega328 TIMER0 peripheral is being set to execute the TIMER0 interrupt routine ISR(TIMER0_OVF_vect) on every 10 ms. Using the tenms counter variable, we reduce down to about 200 ms to make the LED display appear nicely in our eyes and at the same time we do the ADC conversion from the National Semiconductor LM35DZ precision centigrade temperature sensor. You could read more about reading the LM35DZ from my previous posted blog AVR LCD Thermometer Using ADC and PWM Project.

You could easily activate the debugging mode by changing the _DEBUG_MODE definition value to 1 before compiling the program and use the serial terminal program to watch the result.

// Debugging Mode, 0 - Debug OFF, 1 - Debug ON
#define _DEBUG_MODE   1

This simple embedded Web server C code only took about 6196 bytes of the flash RAM and 816 bytes of the SRAM (debug mode turn off). The whole C code in this project is designed to be just a single file (i.e. wiznetweb.c) and all you need is the standard Win-AVR GCC includes file in order to make this program compiled successfully in the Atmel AVR Studio environment as shown on this following picture:

Now you could enjoy the embedded web server project video on this tutorial. In this video I used both acer aspire notebook and BlackBerry Javelin 8900 smart phone to access the embedded web server which is connected to the Linksys Wireless-G 2.4GHz broadband router:

The Final Thought

Actually the W5100 socket driver for the Atmel AVR microcontroller families has been provided by the Wiznet (version 1.5, as I published this blog), you could download the driver from their site (http://www.wiznet.co.kr). This driver (version 1.4) is adapted and used in the Arduino framework environment, known as the Arduino Ethernet Shield library.

Of course if I use the Wiznet socket driver in this project then you won’t learn anything, therefore to honor the spirit of experimenting inside every electronics hobbyists I decided to write my own version of the Wiznet W5100 socket driver for this project. Hopefully this project will give you a better understanding of how to integrate the Wiznet W5100 TCP/IP hardwire chip in your next embedded system application.

posted @ 2011-09-01 15:48  pulan  阅读(1203)  评论(0编辑  收藏  举报