用于ATmega设备的Wiznet W5100库

Home / 博客 / 用于ATmega设备的Wiznet W5100库

几个月前我就开始研究W5100芯片而且非常喜欢它。我建立了一个测试平台,并使用ATmaga644p通过总线连接W5100(在WIZ811MJ开发板上)。

一开始,我从ermicroblog网站上下载并加载代码。这些代码都是由RWB开发的,他开创了一个很好的开端。我尤其是喜欢RWB一步一步演绎一个完整例子的过程,有一行行的代码作为理论基础,没有头文件会丢失,也不需要后台代码作支撑。

我重新编写了RWB的代码,并且创建一个W5100设备库。请注意这个库内的目标是相互独立的。这就意味着你可以链接任何ATmega项目的代码而不需要每次都对库进行重建,因为你的硬件和我的不兼容。这种独立性来自一个叫做W51_register()的成熟调用例程,它允许你通过函数指针设置到库内进行具体的目标测试,比如启用/禁用W5100芯片和通过设备进行数据交换。

W5100库代码

以下是W5100库模块的代码。不像RWB的代码,我的代码使用了一个头文件(源于RWB的代码和微知纳特用户手册的结合);你可以在页面最底部的的压缩包文件看到它。

/*
*  w5100.c      library of target-independent AVR support routines
*               for the Wiznet W5100 Ethernet interface device
*
*  This file is derived from the excellent work found here:
www.ermicro.com/blog/?p=1773
*  by RWB.  I am leaving the header from the original file intact below,
*  but you need to remember the rest of the source here is fairly
*  heavily modified.  Go to the above site for the original.
*/

/*****************************************************************************
//  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
*****************************************************************************/

/*
*  The following code turns the above wiznetping.c source code into a
*  generic library of W5100 support routines that are target-independent.
*  That is, you build this library for a generic AVR ATmega device, then
*  write your application to use the W51_xxx routines below for accessing
*  the W5100 chip.  Because these routines are target-independent, you
*  never have to rebuild them just because you are moving your code from,
*  say, a ‘mega128 to an ‘xmega128a1 device.
*
*  For this to work properly, your application must provide three target-
*  specific functions and must register the addresses of those functions
*  with the W5100 library at run-time.  These functions are:
*
*  select        target-specific function for enabling the W5100 chip
*  xchg        target-specific function for exchanging a byte with the W5100 chip
*  deselect    target-specific function for disabling the W5100 chip
*  reset        target-specific function for hardware reset of the W5100 chip
*
*  Your application registers these three functions with the W5100 library
*  by invoking the W51_register() function.  Your application must make this
*  call one time and must make this call before calling any other W5100
*  functions.
*/

#include <util/delay.h>
#include “w5100.h”

#ifndef  FALSE
#define  FALSE        0
#define  TRUE        !FALSE
#endif

/*
*  Define the function pointers used to access the SPI port assigned to the
*  W5100 device.  These pointers will be filled in at run-time when the host
*  calls W51_register().
*/
static  void                    (*select)(void) = (void *)0;
static  unsigned char            (*xchg)(unsigned char  val) = (void *)0;
static  void                    (*deselect)(void) = (void *)0;
static  void                    (*reset)(void) = (void *)0;

static  unsigned char            inited = FALSE;

void  W51_register(W5100_CALLBACKS  *pcallbacks)
{
select = pcallbacks->_select;
xchg = pcallbacks->_xchg;
deselect = pcallbacks->_deselect;
reset = pcallbacks->_reset;
inited = FALSE;
if ((select) && (xchg) && (deselect))  inited = TRUE;    // these functions must be valid
}

void  W51_write(unsigned int  addr, unsigned char  data)
{
if (!inited)  return;                        // not set up, ignore request

select();                                    // enable the W5100 chip
xchg(W5100_WRITE_OPCODE);                    // need to write a byte
xchg((addr & 0xff00) >> 8);                // send MSB of addr
xchg(addr & 0xff);                            // send LSB
xchg(data);                                    // send the data
deselect();                                    // done with the chip
}

unsigned char  W51_read(unsigned int  addr)
{
unsigned char                val;

if (!inited)  return  0;                    // not set up, ignore request

select();                                    // enable the W5100 chip
xchg(W5100_READ_OPCODE);                    // need to read a byte
xchg((addr & 0xff00) >> 8);                // send MSB of addr
xchg(addr & 0xff);                            // send LSB
val = xchg(0x00);                            // need to send a dummy char to get response
deselect();                                    // done with the chip
return  val;                                // tell her what she’s won
}

void  W51_init(void)
{
if (reset)  reset();                        // if host provided a reset function, use it
else        W51_write(W5100_MR, W5100_MR_SOFTRST);         // otherwise, force the w5100 to soft-reset
_delay_ms(1);
}

unsigned char  W51_config(W5100_CFG  *pcfg)
{
if (pcfg == 0)  return  W5100_FAIL;

W51_write(W5100_GAR + 0, pcfg->gtw_addr[0]);    // set up the gateway address
W51_write(W5100_GAR + 1, pcfg->gtw_addr[1]);
W51_write(W5100_GAR + 2, pcfg->gtw_addr[2]);
W51_write(W5100_GAR + 3, pcfg->gtw_addr[3]);
_delay_ms(1);

W51_write(W5100_SHAR + 0, pcfg->mac_addr[0]);    // set up the MAC address
W51_write(W5100_SHAR + 1, pcfg->mac_addr[1]);
W51_write(W5100_SHAR + 2, pcfg->mac_addr[2]);
W51_write(W5100_SHAR + 3, pcfg->mac_addr[3]);
W51_write(W5100_SHAR + 4, pcfg->mac_addr[4]);
W51_write(W5100_SHAR + 5, pcfg->mac_addr[5]);
_delay_ms(1);

W51_write(W5100_SUBR + 0, pcfg->sub_mask[0]);    // set up the subnet mask
W51_write(W5100_SUBR + 1, pcfg->sub_mask[1]);
W51_write(W5100_SUBR + 2, pcfg->sub_mask[2]);
W51_write(W5100_SUBR + 3, pcfg->sub_mask[3]);
_delay_ms(1);

W51_write(W5100_SIPR + 0, pcfg->ip_addr[0]);    // set up the source IP address
W51_write(W5100_SIPR + 1, pcfg->ip_addr[1]);
W51_write(W5100_SIPR + 2, pcfg->ip_addr[2]);
W51_write(W5100_SIPR + 3, pcfg->ip_addr[3]);
_delay_ms(1);

W51_write(W5100_RMSR, 0x55);                    // use default buffer sizes (2K bytes RX and TX for each socket
W51_write(W5100_TMSR, 0x55);

return  W5100_OK;                                // everything worked, show success
}

使用库

下面来教你使用这个库。创建一个新的AVRStudio4项目,并且在你的项目(记得修改你的项目配置中文件夹的大小,以便能放得下W5100.h文件)里添加W5100.h头文件。同时修改你的项目配置来添加W5100库,以便你在建立项目时连接器能找到它。

在你的资源库中,你需要写下三个(也可以是4个)具体目标例程。这三个必需的例程可以实现通过总线访问电路板中的W5100芯片。select()例程用来选择端口线与W5100设备进行连接。deselect()例程的作用是取消对W5100设备的选择。xchg()例程的作用是,每当通过总线向W5100发送一个字节的数据,会同时向调用例程返回一个字节的数据。有时候,你可能还会用到reset()例程,它的作用是使端口线重置W5100设备。我真心建议你将来在硬件设置中使用它,因为有时在W5100启动时会遇到一些故障,到时候你对它重置,就省事多了。

项目案例(网络服务器)

以下代码是使用库建立的网络服务器案例。

#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include “w5100.h”

unsigned char            OpenSocket(unsigned char  sock, unsigned char  eth_protocol, unsigned int  tcp_port);
void                    CloseSocket(unsigned char  sock);
void                    DisconnectSocket(unsigned char  sock);
unsigned char            Listen(unsigned char  sock);
unsigned char            Send(unsigned char  sock, const unsigned char  *buf, unsigned int  buflen);
unsigned int            Receive(unsigned char  sock, unsigned char  *buf, unsigned int  buflen);
unsigned int            ReceivedSize(unsigned char  sock);

void                    my_select(void);
void                    my_deselect(void);
unsigned char            my_xchg(unsigned char  val);
void                    my_reset(void);

#define  MAX_BUF                    512            /* largest buffer we can read from chip */

#define  HTTP_PORT                    80            /* TCP port for HTTP */

/*
*  Ethernet setup
*
*  Define the MAC address, IP address, subnet mask, and gateway
*  address for the target device.
*/
W5100_CFG                    my_cfg =
{
{0x00,0x16,0x36,0xDE,0x58,0xF6},            // mac_addr
{192,168,1,233},                            // ip_addr
{255,255,255,0},                            // sub_mask
{192,168,1,1}                                // gtw_addr
};

/*
*  Callback function block
*
*  Define callback functions for target-independent support of the
*  W5100 chip.  Here is where you store pointers to the various
*  functions needed by the W5100 library code.  These functions all
*  handle tasks that are target-dependent, which means the library
*  code can be target-INdependent.
*/
W5100_CALLBACKS                my_callbacks;

unsigned char                        buf[MAX_BUF];

unsigned char  OpenSocket(unsigned char  sock, unsigned char  eth_protocol, unsigned int  tcp_port)
{
unsigned char            retval;
unsigned int            sockaddr;

retval = W5100_FAIL;                            // assume this doesn’t work
if (sock >= W5100_NUM_SOCKETS)  return retval;    // illegal socket value is bad!

sockaddr =  W5100_SKT_BASE(sock);                // calc base addr for this socket

if (W51_read(sockaddr+W5100_SR_OFFSET) == W5100_SKT_SR_CLOSED)    // Make sure we close the socket first
{
CloseSocket(sock);
}

W51_write(sockaddr+W5100_MR_OFFSET ,eth_protocol);        // set protocol for this socket
W51_write(sockaddr+W5100_PORT_OFFSET, ((tcp_port & 0xFF00) >> 8 ));        // set port for this socket (MSB)
W51_write(sockaddr+W5100_PORT_OFFSET + 1, (tcp_port & 0x00FF));            // set port for this socket (LSB)
W51_write(sockaddr+W5100_CR_OFFSET, W5100_SKT_CR_OPEN);                       // open the socket

while (W51_read(sockaddr+W5100_CR_OFFSET))  ;            // loop until device reports socket is open (blocks!!)

if (W51_read(sockaddr+W5100_SR_OFFSET) == W5100_SKT_SR_INIT)  retval = sock;    // if success, return socket number
else  CloseSocket(sock);                                // if failed, close socket immediately

return  retval;
}

void  CloseSocket(unsigned char  sock)
{
unsigned int            sockaddr;

if (sock > W5100_NUM_SOCKETS)  return;            // if illegal socket number, ignore request
sockaddr = W5100_SKT_BASE(sock);                // calc base addr for this socket

W51_write(sockaddr+W5100_CR_OFFSET, W5100_SKT_CR_CLOSE);    // tell chip to close the socket
while (W51_read(sockaddr+W5100_CR_OFFSET))  ;    // loop until socket is closed (blocks!!)
}

void  DisconnectSocket(unsigned char  sock)
{
unsigned int            sockaddr;

if (sock > W5100_NUM_SOCKETS)  return;            // if illegal socket number, ignore request
sockaddr = W5100_SKT_BASE(sock);                // calc base addr for this socket

W51_write(sockaddr+W5100_CR_OFFSET, W5100_SKT_CR_DISCON);        // disconnect the socket
while (W51_read(sockaddr+W5100_CR_OFFSET))  ;    // loop until socket is closed (blocks!!)
}

unsigned char  Listen(unsigned char  sock)
{
unsigned char            retval;
unsigned int            sockaddr;

retval = W5100_FAIL;                            // assume this fails
if (sock > W5100_NUM_SOCKETS)  return  retval;    // if illegal socket number, ignore request

sockaddr = W5100_SKT_BASE(sock);                // calc base addr for this socket
if (W51_read(sockaddr+W5100_SR_OFFSET) == W5100_SKT_SR_INIT)    // if socket is in initialized state…
{
W51_write(sockaddr+W5100_CR_OFFSET, W5100_SKT_CR_LISTEN);        // put socket in listen state
while (W51_read(sockaddr+W5100_CR_OFFSET))  ;        // block until command is accepted

if (W51_read(sockaddr+W5100_SR_OFFSET) == W5100_SKT_SR_LISTEN)  retval = W5100_OK;    // if socket state changed, show success
else  CloseSocket(sock);                    // not in listen mode, close and show an error occurred
}
return  retval;
}

unsigned char  Send(unsigned char  sock, const unsigned char  *buf, unsigned int  buflen)
{
unsigned int                    ptr;
unsigned int                    offaddr;
unsigned int                    realaddr;
unsigned int                    txsize;
unsigned int                    timeout;
unsigned int                    sockaddr;

if (buflen == 0 || sock >= W5100_NUM_SOCKETS)  return  W5100_FAIL;        // ignore illegal requests
sockaddr = W5100_SKT_BASE(sock);            // calc base addr for this socket
// Make sure the TX Free Size Register is available
txsize = W51_read(sockaddr+W5100_TX_FSR_OFFSET);        // make sure the TX free-size reg is available
txsize = (((txsize & 0x00FF) << 8 ) + W51_read(sockaddr+W5100_TX_FSR_OFFSET + 1));

timeout = 0;
while (txsize < buflen)
{
_delay_ms(1);

txsize = W51_read(sockaddr+W5100_TX_FSR_OFFSET);        // make sure the TX free-size reg is available
txsize = (((txsize & 0x00FF) << 8 ) + W51_read(sockaddr+W5100_TX_FSR_OFFSET + 1));

if (timeout++ > 1000)                         // if max delay has passed…
{
DisconnectSocket(sock);                    // can’t connect, close it down
return  W5100_FAIL;                        // show failure
}
}

// Read the Tx Write Pointer
ptr = W51_read(sockaddr+W5100_TX_WR_OFFSET);
offaddr = (((ptr & 0x00FF) << 8 ) + W51_read(sockaddr+W5100_TX_WR_OFFSET + 1));

while (buflen)
{
buflen–;
realaddr = W5100_TXBUFADDR + (offaddr & W5100_TX_BUF_MASK);        // calc W5100 physical buffer addr for this socket

W51_write(realaddr, *buf);                    // send a byte of application data to TX buffer
offaddr++;                                    // next TX buffer addr
buf++;                                        // next input buffer addr
}

W51_write(sockaddr+W5100_TX_WR_OFFSET, (offaddr & 0xFF00) >> 8);    // send MSB of new write-pointer addr
W51_write(sockaddr+W5100_TX_WR_OFFSET + 1, (offaddr & 0x00FF));        // send LSB

W51_write(sockaddr+W5100_CR_OFFSET, W5100_SKT_CR_SEND);    // start the send on its way
while (W51_read(sockaddr+W5100_CR_OFFSET))  ;    // loop until socket starts the send (blocks!!)

return  W5100_OK;
}

/*
*  Define the SPI port, used to exchange data with a W5100 chip.
*/
#define SPI_PORT     PORTB                /* target-specific port containing the SPI lines */
#define SPI_DDR      DDRB                /* target-specific DDR for the SPI port lines */

#define CS_DDR        DDRD                /* target-specific DDR for chip-select */
#define CS_PORT     PORTD                /* target-specific port used as chip-select */
#define CS_BIT        2                    /* target-specific port line used as chip-select */

#define RESET_DDR   DDRD                /* target-specific DDR for reset */
#define RESET_PORT    PORTD                /* target-specific port used for reset */
#define RESET_BIT    3                    /* target-specific port line used as reset */

/*
*  Define macros for selecting and deselecting the W5100 device.
*/
#define  W51_ENABLE        CS_PORT&=~(1<<CS_BIT)
#define  W51_DISABLE    CS_PORT|=(1<<CS_BIT)

unsigned int  Receive(unsigned char  sock, unsigned char  *buf, unsigned int  buflen)
{
unsigned int                    ptr;
unsigned int                    offaddr;
unsigned int                    realaddr;
unsigned int                    sockaddr;

if (buflen == 0 || sock >= W5100_NUM_SOCKETS)  return  W5100_FAIL;        // ignore illegal conditions

if (buflen > (MAX_BUF-2))  buflen = MAX_BUF – 2;        // requests that exceed the max are truncated

sockaddr = W5100_SKT_BASE(sock);                        // calc base addr for this socket
ptr = W51_read(sockaddr+W5100_RX_RD_OFFSET);            // get the RX read pointer (MSB)
offaddr = (((ptr & 0x00FF) << 8 ) + W51_read(sockaddr+W5100_RX_RD_OFFSET + 1));        // get LSB and calc offset addr

while (buflen)
{
buflen–;
realaddr = W5100_RXBUFADDR + (offaddr & W5100_RX_BUF_MASK);
*buf = W51_read(realaddr);
offaddr++;
buf++;
}
*buf=’\0′;                                                 // buffer read is complete, terminate the string

// Increase the S0_RX_RD value, so it point to the next receive
W51_write(sockaddr+W5100_RX_RD_OFFSET, (offaddr & 0xFF00) >> 8);    // update RX read offset (MSB)
W51_write(sockaddr+W5100_RX_RD_OFFSET + 1,(offaddr & 0x00FF));        // update LSB

// Now Send the RECV command
W51_write(sockaddr+W5100_CR_OFFSET, W5100_SKT_CR_RECV);            // issue the receive command
_delay_us(5);                                            // wait for receive to start

return  W5100_OK;
}

unsigned int  ReceivedSize(unsigned char  sock)
{
unsigned int                    val;
unsigned int                    sockaddr;

if (sock >= W5100_NUM_SOCKETS)  return  0;
sockaddr = W5100_SKT_BASE(sock);                        // calc base addr for this socket
val = W51_read(sockaddr+W5100_RX_RSR_OFFSET) & 0xff;
val = (val << 8) + W51_read(sockaddr+W5100_RX_RSR_OFFSET + 1);
return  val;
}

/*
*  Simple wrapper function for selecting the W5100 device.  This function
*  allows the library code to invoke a target-specific function for enabling
*  the W5100 chip.
*/
void  my_select(void)
{
W51_ENABLE;
}

/*
*  Simple wrapper function for deselecting the W5100 device.  This function
*  allows the library code to invoke a target-specific function for disabling
*  the W5100 chip.
*/
void  my_deselect(void)
{
W51_DISABLE;
}

/*
*  my_xchg      callback function; exchanges a byte with W5100 chip
*/
unsigned char  my_xchg(unsigned char  val)
{
SPDR = val;
while  (!(SPSR & (1<<SPIF)))  ;
return  SPDR;
}

/*
*  my_reset      callback function; force a hardware reset of the W5100 device
*/
void  my_reset(void)
{
RESET_PORT |= (1<<RESET_BIT);                            // pull reset line high
RESET_DDR |= (1<<RESET_BIT);                            // now make it an output
RESET_PORT &= ~(1<<RESET_BIT);                            // pull the line low
_delay_ms(5);                                            // let the device reset
RESET_PORT |= (1<<RESET_BIT);                            // done with reset, pull the line high
_delay_ms(10);                                            // let the chip wake up
}

int  main(void)
{
unsigned int                    sockaddr;
unsigned char                    mysocket;
unsigned int                    rsize;

mysocket = 0;                                            // magic number! declare the socket number we will use (0-3)
sockaddr = W5100_SKT_BASE(mysocket);                    // calc address of W5100 register set for this socket

/*
*  Initialize the ATmega644p SPI subsystem
*/
CS_PORT |= (1<<CS_BIT);                                    // pull CS pin high
CS_DDR |= (1<<CS_BIT);                                    // now make it an output

SPI_PORT = SPI_PORT | (1<<PORTB4);                        // make sure SS is high
SPI_DDR = (1<<PORTB4)|(1<<PORTB5)|(1<<PORTB7);            // set MOSI, SCK and SS as output, others as input
SPCR = (1<<SPE)|(1<<MSTR);                                // enable SPI, master mode 0
SPSR |= (1<<SPI2X);                                        // set the clock rate fck/2

/*
*  Load up the callback block, then initialize the Wiznet W5100
*/
my_callbacks._select = &my_select;                        // callback for selecting the W5100
my_callbacks._xchg = &my_xchg;                            // callback for exchanging data
my_callbacks._deselect = &my_deselect;                    // callback for deselecting the W5100
my_callbacks._reset = &my_reset;                        // callback for hardware-reset of the W5100

W51_register(&my_callbacks);                            // register our target-specific W5100 routines with the W5100 library
W51_init();                                                // now initialize the W5100

/*
*  Configure the W5100 device to handle PING requests.
*  This requires configuring the chip, not a specific socket.
*/
W51_config(&my_cfg);                                    // config the W5100 (MAC, TCP address, subnet, etc

/*
*  The main loop.  Control stays in this loop forever, processing any received packets
*  and sending any requested data.
*/
while  (1)
{
switch  (W51_read(sockaddr+W5100_SR_OFFSET))        // based on current status of socket…
{
case  W5100_SKT_SR_CLOSED:                        // if socket is closed…
if (OpenSocket(mysocket, W5100_SKT_MR_TCP, HTTP_PORT) == mysocket)        // if successful opening a socket…
{
Listen(mysocket);
_delay_ms(1);
}
break;

case  W5100_SKT_SR_ESTABLISHED:                    // if socket connection is established…
rsize = ReceivedSize(mysocket);                    // find out how many bytes
if (rsize > 0)
{
if (Receive(mysocket, buf, rsize) != W5100_OK)  break;    // if we had problems, all done
/*
*  Add code here to process the payload from the packet.
*
*  For now, we just ignore the payload and send a canned HTML page so the client at least
*  knows we are alive.
*/
strcpy_P((char *)buf, PSTR(“HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n”));
strcat_P((char *)buf, PSTR(“<html>\r\n<body>\r\n”));
strcat_P((char *)buf, PSTR(“<title>Karl’s W5100 web server (ATmega644p)</title>\r\n”));
strcat_P((char *)buf, PSTR(“<h2>Karl’s ATmega644p web server using Wiznet W5100 chip</h2>\r\n”));
strcat_P((char *)buf, PSTR(“<br /><hr>\r\n”));
if (Send(mysocket, buf, strlen((char *)buf)) == W5100_FAIL)  break;        // just throw out the packet for now

strcpy_P((char *)buf, PSTR(“This is part 2 of the page.”));
strcat_P((char *)buf, PSTR(“</body>\r\n</html>\r\n”));
if (Send(mysocket, buf, strlen((char *)buf)) == W5100_FAIL)  break;        // just throw out the packet for now

DisconnectSocket(mysocket);
}
else                                            // no data yet…
{
_delay_us(10);
}
break;

case  W5100_SKT_SR_FIN_WAIT:
case  W5100_SKT_SR_CLOSING:
case  W5100_SKT_SR_TIME_WAIT:
case  W5100_SKT_SR_CLOSE_WAIT:
case  W5100_SKT_SR_LAST_ACK:
CloseSocket(mysocket);
break;
}
}

return  0;
}

关于网络服务器

上面的代码改编自RWB的源代码,因为源代码中有太多的限制因素。基本上这些代码都不解析服务器收到的数据包,它只负责固定和抛送文件。对于确认服务器的工作状态来说,这已经足够好了,但是对于完成一个项目来讲就差得远了。在网页中浏览RWB的代码,查找相似项目中的所有细节,他是怎么解析数据包并且创建网络服务器的。请注意我也删除了RWB源代码中所有USART的例程(但还是无效)。

我已经改编了4个RWB的W5100套接字源代码,你可以直接使用它们。这些代码都使用套接字0进行过测试,但还没经过其他的套接字测试,也没有进行多个套接字同时测试。

以上的代码,加上链接连到库里的,总共有2.4千字节数据量。是的!这就是不超过2.5千字节的ATmega644p网络服务器代码!

为了修改这些代码更好地被你所用,你需要重新编译回调函数(就是上面的main()主函数)来适应你的硬件。同时你也需要修改W5100_CFG的结构项来匹配你的网络;我在上面的my_cfg中有声明。最后一步就是重建并且链接W5100库模块,你应该会比我做得更好。

 

编译:WIZnet BJ – Ben

原文地址:Wiznet 5100 library for ATmega devices