都说它简单易用,买了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.