第十三章 W55MH32 UPnP端口转发示例 单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机
W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。
在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。
W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入http://www.w5500.com/网站或者私信获取。
此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。
为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。
若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页:http://www.w5500.com/,我们期待与您共同探索W55MH32的无限可能。

第十三章 W55MH32 UPnP端口转发示例
本篇文章,我们将详细介绍如何在W55MH32芯片上面实现UPnP协议。使用W55MH32的TOE引擎,我们只需进行简单的socket编程及寄存器读写,便可轻松实现以太网应用。接下来我们通过实战例程,为大家讲解如何使用TOE引擎实现UPnP协议的端口转发功能。
该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关W55MH32的初始化过程,请参考Network Install章节,这里将不再赘述。
1 UPnP协议简介
UPnP(Universal Plug and Play)协议是一种支持设备在局域网中实现自动发现和通信的网络协议。其端口转发功能由IGD Profile提供,允许局域网设备动态请求路由器为其开放指定的端口,以实现外部设备访问内部服务。这种功能消除了手动配置端口转发的复杂性,特别适用于需要穿透NAT(网络地址转换)环境的应用场景。
IGD(Internet Gateway Device,互联网网关设备)是UPnP(Universal Plug and Play)协议的一部分,主要用于管理网络中的网关设备(如路由器)的服务和资源。IGD扩展定义了一套标准接口,允许局域网设备与网关设备通信,动态配置网络设置,例如端口转发、带宽管理和连接状态查询等。
2 UPnP协议特点
自动化配置:无需用户手动设置,减少了配置错误的风险。
动态灵活:端口映射规则可以根据需求动态添加或删除。
设备友好:支持即插即用,简化了设备的联网和部署过程。
跨设备兼容:UPnP基于标准化协议,广泛支持各种设备和平台。
3 UPnP应用场景
通过UPnP端口转发功能,我们可以使用W55MH32实现以下功能:
远程访问:将外部请求转发到局域网设备(如NAS、监控摄像头),实现外部远程访问内部设备。
远程控制:外部设备通过UPnP转换的端口,可以实现远程控制局域网内部设备(智能门锁、灯光控制器)。
4 UPnP设置端口转发的工作流程
设备发现:W55MH32通过SSDP(Simple Service Discovery Protocol)向局域网中发送组播请求(HTTP M-SEARCH报文),搜索支持IGD的网关设备。
获取服务描述:W55MH32访问网关设备(路由器)获取服务描述文件,了解支持的服务和接口。
订阅IGD事件:通过事件订阅,W55MH32可以在不主动轮询的情况下,接收实时通知。
调用服务接口:使用UPnP的SOAT消息调用IGD提供的端口映射接口。
数据交互测试:外部通过访问映射的端口及路由器地址和局域网内部设备进行通信。
5 报文讲解
设备搜索
上文我们提到,设备搜索时使用SSDP协议,SSDP(Simple Service Discovery Protocol)是 UPnP 协议中的关键协议,用于设备发现和服务发布。它通过HTTP over UDP 的形式在局域网内广播和接收报文,采用多播地址 239.255.255.250 和端口 1900。
SSDP报文主要分为以下几类:
NOTIFY 消息(设备主动广播通知):用于设备向网络通告自己的存在或离线状态。
M-SEARCH 消息(客户端主动搜索):客户端发送搜索请求以发现设备或服务。
HTTP/1.1 响应消息(设备对 M-SEARCH 的响应):设备对搜索请求的响应,提供设备描述文件的位置及服务信息。
SSDP报文基于HTTP协议,有固定的格式,主要包括以下字段:
HOST:目标地址和端口,固定为 239.255.255.250:1900。
MAN:用于标识搜索消息,固定为 "ssdp:discover"(仅在 M-SEARCH 中使用)。
MX:最大响应时间,指定设备在多长时间内响应(单位:秒)。
ST:搜索目标,标识要查找的设备类型或服务类型。
NT:通知类型,表示设备或服务的类型(在 NOTIFY 消息中使用)。
USN:唯一服务名称,设备或服务的唯一标识符。
LOCATION:设备描述文件的 URL,包含设备的详细信息。
CACHE-CONTROL:设备信息的缓存时间,表示在多长时间内有效。
M-SEARCH请求报文实例:
M-SEARCH * HTTP/1.1 Host:239.255.255.250:1900 ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1 Man:"ssdp:discover" MX:3
字段解析:
M-SEARCH * HTTP/1.1:表明是一个搜索请求。
Host:多播地址和端口。
ST:搜索目标类型,这里是IGD设备。
MX:最大响应事件,设备需要在3秒内返回响应。
Man:搜索请求类型,固定。
M-SEARCH响应报文实例:
HTTP/1.1 200 OK CACHE-CONTROL: max-age=60 DATE: Tue, 07 Jan 2025 06:43:49 GMT EXT: LOCATION: http://192.168.100.1:1900/igd.xml SERVER: vxWorks/5.5 UPnP/1.0 TL-WR886N/6.0 ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1 USN: uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9::urn:schemas-upnp-org:device:InternetGatewayDevice:1
HTTP/1.1 200 OK:表示响应成功。
CACHE-CONTROL:响应有效时间为60秒。
DATE:响应的时间戳。
EXT:保留字段,目前为空。
LOCATION:设备描述文件的URL。
SERVER:设备的操作系统,UPnP版本和设备名称。
ST:搜索目标类型,和请求中的ST字段一致。
USN:唯一设备标识符。
获取设备标识符
这一步会通过HTTP GET方式去请求xml文件,有关HTTP GET报文以及HTTP响应报文这里不过多讲解,有兴趣的可以参考 HTTP Client章节。
请求示例:
GET /igd.xml HTTP/1.1 Accept: text/xml, application/xml User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) Host: 192.168.100.1:1900 Connection: Keep-Alive Cache-Control: no-cache Pragma: no-cache
响应示例:
HTTP/1.1 200 OK Content-Type: text/xml;charset=UTF-8 Content-Length: 2580 Connection: close Cache-control: no-cache < ?xml version="1.0"? > < root xmlns="urn:schemas-upnp-org:device-1-0" > < specVersion > < major >1< /major > < minor >0< /minor > < /specVersion > < device > < deviceType >urn:schemas-upnp-org:device:InternetGatewayDevice:1< /deviceType > < presentationURL >http://192.168.100.1:80 < /presentationURL > < friendlyName >Wireless N Router TL-WR886N< /friendlyName > < manufacturer >TP-LINK< /manufacturer > < manufacturerURL >http://www.tp-link.com.cn< /manufacturerURL > < modelDescription >TL-WR886N 6.0< /modelDescription > < modelName >TL-WR886N< /modelName > < modelNumber >6.0< /modelNumber > < UDN >uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9< /UDN > < UPC >123456789001< /UPC > < serviceList > < service > < serviceType >urn:schemas-upnp-org:service:Layer3Forwarding:1< /serviceType > < serviceId >urn:upnp-org:serviceId:L3Forwarding1< /serviceId > < controlURL >/l3f< /controlURL > < eventSubURL >/l3f< /eventSubURL > < SCPDURL >/l3f.xml< /SCPDURL > < /service > < /serviceList > < deviceList > < device > < deviceType >urn:schemas-upnp-org:device:WANDevice:1< /deviceType > < friendlyName >WAN Device< /friendlyName > < manufacturer >TP-LINK< /manufacturer > < manufacturerURL >http://www.tp-link.com.cn< /manufacturerURL > < modelDescription >WAN Device< /modelDescription > < modelName >WAN Device< /modelName > < modelNumber >1.0< /modelNumber > < modelURL >< /modelURL > < serialNumber >12345678900001< /serialNumber > < UDN >uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9< /UDN > < UPC >123456789001< /UPC > < serviceList > < service > < serviceType >urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1< /serviceType > < serviceId >urn:upnp-org:serviceId:WANCommonInterfaceConfig< /serviceId > < controlURL >/ifc< /controlURL > < eventSubURL >/ifc< /eventSubURL > < SCPDURL >/ifc.xml< /SCPDURL > < /service > < /serviceList > < deviceList > < device > < deviceType >urn:schemas-upnp-org:device:WANConnectionDevice:1< /deviceType > < friendlyName >WAN Connection Device< /friendlyName > < manufacturer >TP-LINK< /manufacturer > < manufacturerURL >http://www.tp-link.com.cn< /manufacturerURL > < modelDescription >WAN Connection Device< /modelDescription > < modelName >WAN Connection Device< /modelName > < modelNumber >1.0< /modelNumber > < modelURL >< /modelURL > < serialNumber >12345678900001< /serialNumber > < UDN >uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9< /UDN > < UPC >123456789001< /UPC > < serviceList > < service > < serviceType >urn:schemas-upnp-org:service:WANIPConnection:1< /serviceType > < serviceId >urn:upnp-org:serviceId:WANIPConnection< /serviceId > < controlURL >/ipc< /controlURL > < eventSubURL >/ipc< /eventSubURL > < SCPDURL >/ipc.xml< /SCPDURL > < /service > < /serviceList > < /device > < /deviceList > < /device > < /deviceList > < /device > < /root >
订阅IGD事件
通过HTTP SUBSCRIBE订阅IGD事件,示例:
SUBSCRIBE /ipc HTTP/1.1 Host: 192.168.100.1:1900 USER-AGENT: Mozilla/4.0 (compatible; UPnP/1.1; Windows NT/5.1) CALLBACK: < http://192.168.100.101:5002/ > NT: upnp:event TIMEOUT: Second-1800
响应示例:
HTTP/1.1 200 OK Content-Type: text/xml;charset=UTF-8 Content-Length: 0 Connection: close Cache-control: no-cache Server: vxWorks/5.5 UPnP/1.0 TL-WR886N/6.0 Timeout: Second-1800 SID: uuid:82-2150160019
添加映射端口报文
例如,我们想映射TCP协议的内部端口8000到外部端口1000上,可以按照以下示例进行HTTP请求:
POST /ipc HTTP/1.1 Content-Type: text/xml; charset="utf-8" SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping" User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) Host: 192.168.100.1:1900 Content-Length: 1131 Connection: Keep-Alive Cache-Control: no-cache Pragma: no-cache < ?xml version="1.0"? > < SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" > < SOAP-ENV:Body > < m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1" > < NewRemoteHost xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" > < /NewRemoteHost > < NewExternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2" > 1000< /NewExternalPort > < NewProtocol xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" > TCP< /NewProtocol > < NewInternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2" > 8000< /NewInternalPort > < NewInternalClient xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" > 192.168.100.101< /NewInternalClient > < NewEnabled xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="boolean" >1< /NewEnabled > < NewPortMappingDescription xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >W5500_uPnPGetway< /NewPortMappingDescription > < NewLeaseDuration xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4" >0< /NewLeaseDuration > < /m:AddPortMapping > < /SOAP-ENV:Body > < /SOAP-ENV:Envelope >
主要字段的描述如下:
m:AddPortMapping:添加端口映射
NewExternalPort:外部端口号
NewProtocol:协议类型
NewInternalPort:内部端口号
NewInternalClient:内部地址
响应内容:
HTTP/1.1 200 OK Content-Type: text/xml;charset=UTF-8 Content-Length: 289 Connection: close Cache-control: no-cache Server: vxWorks/5.5 UPnP/1.0 TL-WR886N/6.0 < ?xml version="1.0"? > < s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" >< s:Body >< u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1" >< /u:AddPortMappingResponse >< /s:Body >< /s:Envelope >
删除端口映射报文
例如,我们想删除上面映射的1000端口,可以按照以下示例进行HTTP请求:
POST /ipc HTTP/1.1 Content-Type: text/xml; charset="utf-8" SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping" User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) Host: 192.168.100.1:1900 Content-Length: 604 Connection: Keep-Alive Cache-Control: no-cache Pragma: no-cache < ?xml version="1.0"? > < SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" >< SOAP-ENV:Body > < m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1" > < NewRemoteHost xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >< /NewRemoteHost > < NewExternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2" >1000< /NewExternalPort > < NewProtocol xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >TCP< /NewProtocol > < /m:DeletePortMapping > < /SOAP-ENV:Body >< /SOAP-ENV:Envelope >
主要字段的描述如下:
m:DeletePortMapping:删除端口映射
NewExternalPort:外部端口号
NewProtocol:协议类型
NewRemoteHost:外部访问来源,可以为空
6 实现过程
在这个例程中,我们实现了通过串口控制LED灯开关、获取和设置网络地址信息、TCP和UDP回环数据测试以及UPnP添加映射端口和删除映射端口的功能。
注意:测试实例需要W55MH32接入在支持UPnP端口转发的路由器下。
步骤1:设置以太网缓存大小
static uint8_t tx_size[_WIZCHIP_SOCK_NUM_] = {4, 4, 2, 1, 1, 1, 1, 2};
static uint8_t rx_size[_WIZCHIP_SOCK_NUM_] = {4, 4, 2, 1, 1, 1, 1, 2};
/* socket rx and tx buff init */
wizchip_init(tx_size, rx_size);
在这里我们给socket0-7的收发缓存分别设置为4KB,4KB,2KB,1KB,1KB,1KB,1KB,2KB。
其中socket0用于UPnP协议处理,socket1用于TCP和UDP回环处理,socket2用于监听IGD事件。
步骤2:LED控制函数注册
UserLED_Control_Init(set_user_led_status);
set_user_led_status()函数为控制LED的函数,具体内容如下:
/*void set_user_led_status(uint8_t val)
{
if (val)
{
GPIO_SetBits(GPIOD, GPIO_Pin_14);
}
else
{
GPIO_ResetBits(GPIOD, GPIO_Pin_14);
}
}
步骤3:搜索UPnP设备
do
{
printf("Send SSDP.. rn");
} while (SSDPProcess(SOCKET_ID) != 0); // SSDP Search discovery
/**< SSDP Header */
unsigned char SSDP[] = "
M-SEARCH * HTTP/1.1rn
Host:239.255.255.250:1900rn
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1rn
Man:"ssdp:discover"rn
MX:3rn
rn
";
/**
* @brief This function processes the SSDP message.
* @return 0: success, -1: reply packet timeout, 1: received SSDP parse error
*/
signed char SSDPProcess(SOCKET sockfd)
{
char ret_value = 0;
long endTime = 0;
unsigned char mcast_addr[4] = {239, 255, 255, 250};
// unsigned char_t_t mcast_mac[6] = {0x28, 0x2C, 0xB2, 0xE9, 0x42, 0xD6};
unsigned char recv_addr[4];
unsigned short recv_port;
// UDP Socket Open
close(sockfd);
socket(sockfd, Sn_MR_UDP, PORT_SSDP, 0); /*Initialize socket for socket 0*/
while (getSn_SR(sockfd) != SOCK_UDP);
#ifdef UPNP_DEBUG
printf("%srn", SSDP);
#endif
// Send SSDP
if (sendto(sockfd, SSDP, strlen((char *)SSDP), mcast_addr, 1900) <= 0)
printf("SSDP Send error!!!!!!!rn");
// Receive Reply
memset(recv_buffer, '', RECV_BUFFER_SIZE);
endTime = my_time + 3;
while (recvfrom(sockfd, (unsigned char *)recv_buffer, RECV_BUFFER_SIZE, recv_addr, &recv_port) <= 0 && my_time < endTime); // Check Receive Buffer
if (my_time >= endTime)
{ // Check Timeout
close(sockfd);
return -1;
}
// UDP Socket Close
close(sockfd);
#ifdef UPNP_DEBUG
printf("rnReceiveDatarn%srn", recv_buffer);
#endif
// Parse SSDP Message
if ((ret_value = parseSSDP(recv_buffer)) == 0)
UPnP_Step = 1;
return ret_value;
}
在这个函数中,主要是使用SSDP协议搜索IGD设备,发送报文和前面我们介绍的一致。
步骤4:获取IGD设备描述
if (GetDescriptionProcess(SOCKET_ID) == 0) // GET IGD description
{
printf("GetDescription Success!!rn");
}
else
{
printf("GetDescription Fail!!rn");
}
/**
* @brief This function gets the description message from IGD(Internet Gateway Device).
* @return 0: success, -2: Invalid UPnP Step, -1: reply packet timeout, 1: received xml parse error
*/
signed char GetDescriptionProcess(
SOCKET sockfd /**< a socket number. */
)
{
char ret_value = 0;
long endTime = 0;
unsigned long ipaddr;
unsigned short port;
// Check UPnP Step
if (UPnP_Step < 1) return -2;
// Make HTTP GET Header
memset(send_buffer, '', SEND_BUFFER_SIZE);
MakeGETHeader(send_buffer);
#ifdef UPNP_DEBUG
printf("%srn", send_buffer);
#endif
ipaddr = inet_addr((unsigned char *)descIP);
ipaddr = swapl(ipaddr);
port = ATOI(descPORT, 10);
// Connect to IGD(Internet Gateway Device)
close(sockfd);
socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
while (getSn_SR(sockfd) != SOCK_INIT)
{
delay_ms(100);
}
if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
printf("TCP Socket Error!!rn");
// Send Get Discription Message
while ((getSn_SR(sockfd) != SOCK_ESTABLISHED));
send(sockfd, (void *)send_buffer, strlen(send_buffer));
// Receive Reply
memset(recv_buffer, '', RECV_BUFFER_SIZE);
delay_ms(500);
endTime = my_time + 3;
while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
if (my_time >= endTime)
{ // Check Timeout
close(sockfd);
return -1;
}
// TCP Socket Close
close(sockfd);
#ifdef UPNP_DEBUG
printf("rnReceiveDatarn%srn", recv_buffer);
#endif
// Parse Discription Message
if ((ret_value = parseDescription(recv_buffer)) == 0) UPnP_Step = 2;
return ret_value;
}
请求报文通过MakeGETHeader()函数进行组包,具体报文如下:
/**
* @brief This function makes the HTTP GET header.
* @param dest:Target string pointer
* @return none
*/
void MakeGETHeader(char *dest)
{
char local_port[6] = {''};
strcat(dest, "GET ");
strcat(dest, descLOCATION);
strcat(dest, " HTTP/1.1rn");
strcat(dest, "Accept: text/xml, application/xmlrn");
strcat(dest, "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)rn");
strcat(dest, "Host: ");
strcat(dest, descIP);
sprintf(local_port, ":%s", descPORT);
strcat(dest, local_port);
strcat(dest, "rnConnection: Keep-AlivernCache-Control: no-cachernPragma: no-cachernrn");
}
然后将接收到的内容,通过parseDescription()函数进行解析,如果设备描述中不支持WANIPConnection服务,则说明不支持端口映射,返回错误。
parseDescription()函数内容如下:
/**
* @brief This function parses the received description message from IGD(Internet Gateway Dev
* @return 0: success, 1: received xml parse error
*/
signed char parseDescription(
const char *xml /**< string for parse */
)
{
const char controlURL_[] = "";
const char eventSubURL_[] = "";
char *URL_start = 0, *URL_end = 0;
if (parseHTTP(xml) != 0) return 1;
//printf("rn%srn", xml);
// Find Control URL("/etc/linuxigd/gateconnSCPD.ctl")
if ((URL_start = strstr(xml, "urn:schemas-upnp-org:service:WANIPConnection:1")) == NULL) retur
if ((URL_start = strstr(URL_start, controlURL_)) == NULL) return 1;
if ((URL_end = strstr(URL_start, "")) == NULL) return 1;
strncpy(controlURL, URL_start + strlen(controlURL_), URL_end - URL_start - strlen(controlURL_)
// Find Eventing Subscription URL("/etc/linuxigd/gateconnSCPD.evt")
if ((URL_start = strstr(xml, "urn:schemas-upnp-org:service:WANIPConnection:1")) == NULL) retur
if ((URL_start = strstr(URL_start, eventSubURL_)) == NULL) return 1;
if ((URL_end = strstr(URL_start, "")) == NULL) return 1;
strncpy(eventSubURL, URL_start + strlen(eventSubURL_), URL_end - URL_start - strlen(eventSubUR
return 0;
}
步骤5:订阅IGD事件
if (SetEventing(SOCKET_ID) == 0) // Subscribes IGD event messages
{
printf("SetEventing Success!!rn");
}
else
{
printf("SetEventing Fail!!rn");
}
SetEventing()函数内容如下:
/**
* @brief This function subscribes to the eventing message from IGD(Internet Gateway Device).
* @return 0: success, -2: Invalid UPnP Step, -1: reply packet timeout
*/
signed char SetEventing(
SOCKET sockfd /**< a socket number. */
)
{
long endTime = 0;
unsigned long ipaddr;
unsigned short port;
// Check UPnP Step
if (UPnP_Step < 2) return -2;
// Make Subscription message
memset(send_buffer, '', SEND_BUFFER_SIZE);
MakeSubscribe(send_buffer, PORT_UPNP_EVENTING);
#ifdef UPNP_DEBUG
printf("%srn", send_buffer);
#endif
ipaddr = inet_addr((unsigned char *)descIP);
ipaddr = swapl(ipaddr);
port = ATOI(descPORT, 10);
// Connect to IGD(Internet Gateway Device)
close(sockfd);
socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
while (getSn_SR(sockfd) != SOCK_INIT)
{
delay_ms(100);
}
if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
printf("TCP Socket Error!!rn");
// Send Get Discription Message
while ((getSn_SR(sockfd) != SOCK_ESTABLISHED));
send(sockfd, (void *)send_buffer, strlen(send_buffer));
// Receive Reply
memset(recv_buffer, '', RECV_BUFFER_SIZE);
delay_ms(500);
endTime = my_time + 3;
while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
if (my_time >= endTime)
{ // Check Timeout
close(sockfd);
return -1;
}
// TCP Socket Close
close(sockfd);
#ifdef UPNP_DEBUG
printf("rnReceiveDatarn%srn", recv_buffer);
#endif
return parseHTTP(recv_buffer);
}
请求报文通过MakeSubscribe()函数进行组包,具体报文如下:
/**
* @brief This function makes the Subscription message.
* @param dest:Target string pointer
* @param listen_port:Listen port
* @return none
*/
void MakeSubscribe(char *dest, const unsigned int listen_port)
{
char local_port[6] = {''}, ipaddr[16] = {''};
unsigned char ip[4];
strcat(dest, "SUBSCRIBE ");
strcat(dest, eventSubURL);
strcat(dest, " HTTP/1.1rn");
strcat(dest, "Host: ");
strcat(dest, descIP);
sprintf(local_port, ":%s", descPORT);
strcat(dest, local_port);
strcat(dest, "rnUSER-AGENT: Mozilla/4.0 (compatible; UPnP/1.1; Windows NT/5.1)rn");
strcat(dest, "CALLBACK: < http://");
getSIPR(ip);
sprintf(ipaddr, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
strcat(dest, ipaddr);
sprintf(local_port, ":%d/ >", listen_port);
strcat(dest, local_port);
strcat(dest, "rnNT: upnp:eventrnTIMEOUT: Second-1800rnrn");
}
最后,通过parseHTTP()函数解析HTTP响应报文,判断是否订阅成功。
parseHTTP()函数如下:
/*-----String Parse Functions-----*/
/**
* @brief This function parses the HTTP header.
* @return 0: success, 1: received xml parse error
*/
signed char parseHTTP(
const char *xml /**< string for parse */
)
{
char *loc = 0;
if (strstr(xml, "200 OK") != NULL)
return 0;
else
{
loc = strstr(xml, "rn");
memset(content, '', CONT_BUFFER_SIZE);
strncpy(content, xml, loc - xml);
printf("rnHTTP Error:rn%srnrn", content);
return 1;
}
}
步骤6:执行UPnP主程序
Main_Menu(SOCKET_ID, SOCKET_ID + 1, SOCKET_ID + 2, ethernet_buf, tcps_port, udps_port); // Main menu
/**
* @brief Display/Manage a Menu on HyperTerminal Window
* @param sn: use for SSDP; sn2: use for run tcp/udp loopback; sn3: use for listenes IGD event message
* @param buf: use for tcp/udp loopback rx/tx buff; tcps_port: use for tcp loopback listen; udps_port: use
for udp loopback receive
* @return none
*/
void Main_Menu(uint8_t sn, uint8_t sn2, uint8_t sn3, uint8_t *buf, uint16_t tcps_port, uint16_t udps_port)
{
static char choice[3];
static char msg[256], ipaddr[12], protocol[4];
static unsigned short ret, external_port, internal_port;
static uint8_t bTreat;
static uint8_t Sip[4];
while (1)
{
/* Display Menu on HyperTerminal Window */
bTreat = RESET;
printf("rn====================== WIZnet Chip Control Point ===================rn");
printf("This Application is basic example of UART interface withrn");
printf("Windows Hyper Terminal. rn");
printf("rn==========================================================rn");
printf(" APPLICATION MENU :rn");
printf("rn==========================================================rnn");
printf(" 1 - Set LED on rn");
printf(" 2 - Set LED off rn");
printf(" 3 - Show network settingrn");
printf(" 4 - Set network settingrn");
printf(" 5 - Run TCP Loopbackrn");
printf(" 6 - Run UDP Loopbackrn");
printf(" 7 - UPnP PortForwarding: AddPortrn");
printf(" 8 - UPnP PortForwarding: DeletePortrn");
printf("Enter your choice : ");
memset(choice, 0, sizeof(choice));
scanf("%s", choice);
printf("%crn", choice[0]);
在这里会执行一个用户选项菜单,选项1和2控制LED开关,选项3和4打印和设置网络地址信息,选项5运行一个TCP回环测试程序(回环测试程序可参考TCP Server章节),选项6运行一个UDP回环测试程序(回环测试程序可参考UDP章节)。选项7添加一个UPnP端口映射表,选项8删除一个UPnP端口映射表。这里我们主要讲解UPnP相关的选项7和选项8。
步骤7:添加一个UPnP端口映射表
代码如下:
if (choice[0] == '7')
{
bTreat = SET;
printf("rnType a Protocol(TCP/UDP) : ");
memset(msg, 0, sizeof(msg));
scanf("%s", msg);
printf("%srn", msg);
strncpy(protocol, msg, 3);
protocol[3] = '';
printf("rnType a External Port Number : ");
memset(msg, 0, sizeof(msg));
scanf("%s", msg);
printf("%srn", msg);
external_port = ATOI(msg, 10);
printf("rnType a Internal Port Number : ");
memset(msg, 0, sizeof(msg));
scanf("%s", msg);
printf("%srn", msg);
internal_port = ATOI(msg, 10);
if(strcmp(protocol,"tcp") || strcmp(protocol,"TCP"))
tcps_port = internal_port;
else
udps_port = internal_port;
close(sn2);
// Try to Add Port Action
getSIPR(Sip);
sprintf(ipaddr, "%d.%d.%d.%d", Sip[0], Sip[1], Sip[2], Sip[3]);
if ((ret = AddPortProcess(sn, protocol, external_port, ipaddr, internal_port, "W5500_uPnPGetway")) == 0)
printf("AddPort Success!!rn");
else
printf("AddPort Error Code is %drn", ret);
}
在这里,我们需要外部输入端口映射的协议类型(TCP或UDP),以及外部端口号和内部端口号。输入完成后,选项5或选项6的端口号会替换为输入的内部端口号,然后通过AddPortProcess()函数执行添加端口映射处理。AddPortProcess()函数内容如下:
/**
* @brief This function processes the add port to IGD(Internet Gateway Device).
* @return 0: success, -2: Invalid UPnP Step, -1: reply packet timeout, 1: received xml parse error, other: UPnP error code
*/
signed short AddPortProcess(
SOCKET sockfd, /**< a socket number. */
const char *protocol, /**< a procotol name. "TCP" or "UDP" */
const unsigned int extertnal_port, /**< an external port number. */
const char *internal_ip, /**< an internal ip address. */
const unsigned int internal_port, /**< an internal port number. */
const char *description /**< a description of this portforward. */
)
{
short len = 0;
long endTime = 0;
unsigned long ipaddr;
unsigned short port;
// Check UPnP Step
if (UPnP_Step < 2) return -2;
// Make "Add Port" XML(SOAP)
memset(content, '', CONT_BUFFER_SIZE);
MakeSOAPAddControl(content, protocol, extertnal_port, internal_ip, internal_port, description);
// Make HTTP POST Header
memset(send_buffer, '', SEND_BUFFER_SIZE);
len = strlen(content);
MakePOSTHeader(send_buffer, len, ADD_PORT);
strcat(send_buffer, content);
//#ifdef UPNP_DEBUG
printf("%srn", send_buffer);
//#endif
ipaddr = inet_addr((unsigned char *)descIP);
ipaddr = swapl(ipaddr);
port = ATOI(descPORT, 10);
// Connect to IGD(Internet Gateway Device)
socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
while (getSn_SR(sockfd) != SOCK_INIT);
if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
printf("TCP Socket Error!!rn");
// Send "Delete Port" Message
while (getSn_SR(sockfd) != SOCK_ESTABLISHED);
send(sockfd, (void *)send_buffer, strlen(send_buffer));
// Receive Reply
memset(recv_buffer, '', RECV_BUFFER_SIZE);
delay_ms(500);
endTime = my_time + 3;
while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
if (my_time >= endTime)
{ // Check Timeout
close(sockfd);
return -1;
}
// TCP Socket Close
close(sockfd);
//#ifdef UPNP_DEBUG
printf("rnReceiveDatarn%srn", recv_buffer);
//#endif
// Parse Replied Message
return parseAddPort(recv_buffer);
}
程序首先会通过MakeSOAPAddControl()函数组装请求报文中的XML部分,具体内容如下:
/**< SOAP header & tail */
const char soap_start[] =
"
< ?xml version="1.0"? >rn
< SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" >< SOAP-ENV:Body >
";
const char soap_end[] =
"
< /SOAP-ENV:Body >< /SOAP-ENV:Envelope >rn
";
/**< Delete Port Mapping */
const char DeletePortMapping_[] = "< m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1" >";
const char _DeletePortMapping[] = "< /m:DeletePortMapping >";
/**< New Remote Host */
const char NewRemoteHost_[] = "< NewRemoteHost xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >";
const char _NewRemoteHost[] = "< /NewRemoteHost >";
/**< New External Port */
const char NewExternalPort_[] = "< NewExternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2" >";
const char _NewExternalPort[] = "< /NewExternalPort >";
/**< New Protocol */
const char NewProtocol_[] = "< NewProtocol xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >";
const char _NewProtocol[] = "< /NewProtocol >";
/**< Add Port Mapping */
const char AddPortMapping_[] = "< m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1" >";
const char _AddPortMapping[] = "< /m:AddPortMapping >";
/**< New Internal Port */
const char NewInternalPort_[] = "< NewInternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2" >";
const char _NewInternalPort[] = "< /NewInternalPort >";
/**< New Internal Client */
const char NewInternalClient_[] = "< NewInternalClient xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >";
const char _NewInternalClient[] = "< /NewInternalClient >";
/**< New Enabled */
const char NewEnabled[] = "< NewEnabled xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="boolean" >1< /NewEnabled >";
const char NewEnabled_[] = "< NewEnabled xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="boolean" >";
const char _NewEnabled[] = "< /NewEnabled >";
/**< New Port Mapping Description */
const char NewPortMappingDescription_[] = "< NewPortMappingDescription xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string" >";
const char _NewPortMappingDescription[] = "< /NewPortMappingDescription >";
/**< New Lease Duration */
const char NewLeaseDuration[] = "< NewLeaseDuration xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4" >0< /NewLeaseDuration >";
const char NewLeaseDuration_[] = "< NewLeaseDuration xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4" >";
const char _NewLeaseDuration[] = "< /NewLeaseDuration >";
/**
* @brief This function makes the Add Port Control message in SOAP.
* @param dest:Target string pointer
* @param protocol:Protocol type
* @param extertnal_port:External port
* @param internal_ip:Internal IP address
* @param internal_port:Internal port
* @param description:Description
* @return none
*/
void MakeSOAPAddControl(char *dest, const char *protocol, const unsigned int extertnal_port, const char * internal_ip, const unsigned int internal_port, const char *description)
{
char local_port[6] = {''};
strcat(dest, soap_start);
strcat(dest, AddPortMapping_);
strcat(dest, NewRemoteHost_);
strcat(dest, _NewRemoteHost);
strcat(dest, NewExternalPort_);
sprintf(local_port, "%d", extertnal_port);
strcat(dest, local_port);
strcat(dest, _NewExternalPort);
strcat(dest, NewProtocol_);
strcat(dest, protocol);
strcat(dest, _NewProtocol);
strcat(dest, NewInternalPort_);
sprintf(local_port, "%d", internal_port);
strcat(dest, local_port);
strcat(dest, _NewInternalPort);
strcat(dest, NewInternalClient_);
strcat(dest, internal_ip);
strcat(dest, _NewInternalClient);
strcat(dest, NewEnabled);
strcat(dest, NewPortMappingDescription_);
strcat(dest, description);
strcat(dest, _NewPortMappingDescription);
strcat(dest, NewLeaseDuration);
strcat(dest, _AddPortMapping);
strcat(dest, soap_end);
}
然后通过MakePOSTHeader()函数制作HTTP头部内容,具体内容如下:
/**
* @brief This function makes the HTTP POST Header.
* @param dest:Target string pointer
* @param content_length: content length
* @param action: action type
* @return none
*/
void MakePOSTHeader(char *dest, int content_length, int action)
{
char local_length[6] = {''}, local_port[6] = {''};
sprintf(local_length, "%d", content_length);
strcat(dest, "POST ");
strcat(dest, controlURL);
strcat(dest, " HTTP/1.1rn");
strcat(dest, "Content-Type: text/xml; charset="utf-8"rn");
strcat(dest, "SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#");
switch (action)
{
case DELETE_PORT:
strcat(dest, "DeletePortMapping"");
break;
case ADD_PORT:
strcat(dest, "AddPortMapping"");
break;
}
strcat(dest, "rnUser-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)rn");
strcat(dest, "Host: ");
strcat(dest, descIP);
sprintf(local_port, ":%s", descPORT);
strcat(dest, local_port);
strcat(dest, "rnContent-Length: ");
strcat(dest, local_length);
strcat(dest, "rnConnection: Keep-AlivernCache-Control: no-cachernPragma: no-cachernrn");
}
最后则是发送请求,然后通过parseAddPort()函数解析响应内容判断是否添加端口映射成功。
signed short parseAddPort(
const char *xml /**< string for parse */
)
{
parseHTTP(xml);
if (strstr(xml, "u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"") == NULL)
{
return parseError(xml);
}
return 0;
}
步骤8:删除一个UPnP端口映射表
if (choice[0] == '8')
{
bTreat = SET;
printf("rnType a Protocol(TCP/UDP) : ");
memset(msg, 0, sizeof(msg));
scanf("%s", msg);
printf("%srn", msg);
//GetInputString(msg);
strncpy(protocol, msg, 3);
protocol[3] = '';
printf("rnType a External Port Number : ");
// TCP_LISTEN_PORT=num;
// UDP_LISTEN_PORT=num;
// printf("%drn",TCP_LISTEN_PORT);
memset(msg, 0, sizeof(msg));
scanf("%s", msg);
printf("%srn", msg);
external_port = ATOI(msg, 10);
// Try to Delete Port Action
if ((ret = DeletePortProcess(sn, protocol, external_port)) == 0)
printf("DeletePort Success!!rn");
else
printf("DeletePort Error Code is %drn", ret);
}
/* OTHERS CHOICE*/
if (bTreat == RESET)
{
printf(" wrong choice rn");
}
在这里,我们需要外部输入删除端口映射的协议类型(TCP或UDP),以及外部端口号。输入完成后,通过DeletePortProcess()函数执行添加端口映射处理。DeletePortProcess()函数内容如下:
/**
* @brief This function processes the delete port to IGD(Internet Gateway Device).
* @return 0: success, -2: Invalid UPnP Step, -1: reply packet timeout, 1: received xml parse error, other: UPnP error code
*/
signed short DeletePortProcess(
SOCKET sockfd, /**< a socket number. */
const char *protocol, /**< a procotol name. "TCP" or "UDP" */
const unsigned int extertnal_port /**< an external port number. */
)
{
short len = 0;
long endTime = 0;
unsigned long ipaddr;
unsigned short port;
// Check UPnP Step
if (UPnP_Step < 2) return -2;
// Make "Delete Port" XML(SOAP)
memset(content, '', CONT_BUFFER_SIZE);
MakeSOAPDeleteControl(content, protocol, extertnal_port);
// Make HTTP POST Header
memset(send_buffer, '', SEND_BUFFER_SIZE);
len = strlen(content);
MakePOSTHeader(send_buffer, len, DELETE_PORT);
strcat(send_buffer, content);
//#ifdef UPNP_DEBUG
printf("%srn", send_buffer);
//#endif
ipaddr = inet_addr((unsigned char *)descIP);
ipaddr = swapl(ipaddr);
port = ATOI(descPORT, 10);
// Connect to IGD(Internet Gateway Device)
close(sockfd);
socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
while (getSn_SR(sockfd) != SOCK_INIT);
if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
printf("TCP Socket Error!!rn");
// Send "Delete Port" Message
while (getSn_SR(sockfd) != SOCK_ESTABLISHED);
send(sockfd, (void *)send_buffer, strlen(send_buffer));
// Receive Reply
memset(recv_buffer, '', RECV_BUFFER_SIZE);
delay_ms(500);
endTime = my_time + 3;
while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
if (my_time >= endTime)
{ // Check Timeout
close(sockfd);
return -1;
}
// TCP Socket Close
close(sockfd);
//#ifdef UPNP_DEBUG
printf("rnReceiveDatarn%srn", recv_buffer);
//#endif
// Parse Replied Message
return parseDeletePort(recv_buffer);
}
首先会通过MakeSOAPDeleteControl()函数组装请求报文中的XML部分,具体内容如下:
/**
* @brief This function makes the Delete Port Control message in SOAP.
* @param dest:Target string pointer
* @param protocol:Protocol type
* @param extertnal_port:External port
* @return none
*/
void MakeSOAPDeleteControl(char *dest, const char *protocol, const unsigned int extertnal_port)
{
char local_port[6] = {''};
strcat(dest, soap_start);
strcat(dest, DeletePortMapping_);
strcat(dest, NewRemoteHost_);
strcat(dest, _NewRemoteHost);
strcat(dest, NewExternalPort_);
sprintf(local_port, "%d", extertnal_port);
strcat(dest, local_port);
strcat(dest, _NewExternalPort);
strcat(dest, NewProtocol_);
strcat(dest, protocol);
strcat(dest, _NewProtocol);
strcat(dest, _DeletePortMapping);
strcat(dest, soap_end);
}
然后通过MakePOSTHeader()函数制作HTTP头部内容,具体内容如下:
/**
* @brief This function makes the HTTP POST Header.
* @param dest:Target string pointer
* @param content_length: content length
* @param action: action type
* @return none
*/
void MakePOSTHeader(char *dest, int content_length, int action)
{
char local_length[6] = {''}, local_port[6] = {''};
sprintf(local_length, "%d", content_length);
strcat(dest, "POST ");
strcat(dest, controlURL);
strcat(dest, " HTTP/1.1rn");
strcat(dest, "Content-Type: text/xml; charset="utf-8"rn");
strcat(dest, "SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#");
switch (action)
{
case DELETE_PORT:
strcat(dest, "DeletePortMapping"");
break;
case ADD_PORT:
strcat(dest, "AddPortMapping"");
break;
}
strcat(dest, "rnUser-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)rn");
strcat(dest, "Host: ");
strcat(dest, descIP);
sprintf(local_port, ":%s", descPORT);
strcat(dest, local_port);
strcat(dest, "rnContent-Length: ");
strcat(dest, local_length);
strcat(dest, "rnConnection: Keep-AlivernCache-Control: no-cachernPragma: no-cachernrn");
}
最后则是发送请求,然后通过parseDeletePort()函数解析响应内容判断是否添加端口映射成功。
signed short parseDeletePort(
const char *xml /**< string for parse */
)
{
parseHTTP(xml);
if (strstr(xml, "u:DeletePortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"") == NULL)
{
return parseError(xml);
}
return 0;
}
7 运行结果
烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息:

接下来是搜索IGD设备,搜索成功后会进行获取设备描述以及设置订阅IGD事件,全部成功后则进入主菜单。

接着,我们输入7,添加一个TCP协议的端口映射,外部端口为12345,内部端口为8000。

打开UPnP Wizard软件,点击刷新后可以看到我们添加的端口映射表。(UPnP Wizard下载链接:https://upnp-wizard.en.softonic.com/)

然后我们输入5,打开TCP回环测试程序。

随后,我们打开一个网络调试助手,例如SocketTester,选择为TCP Client模式,服务器地址为外部IP地址也就是192.168.1.135,端口号为外部端口号12345,点击”Connect”连接后,可以看到成功连接到内部的W55MH32上了。UDP也是同样进行操作,这里不再演示。

接着我们输入Q退出回环测试程序,然后输入8,将之前添加的TCP协议的12345外部端口删除。在UPnP Wizard上点击刷新,可以看到已经成功删除,再次执行回环测试程序,已经无法连接上内部的W55MH32上。




8 总结
本文讲解了如何在 W55MH32 芯片上实现 UPnP 协议的端口转发功能,通过实战例程详细展示了从设备搜索、获取设备描述、订阅事件到添加和删除端口映射的完整流程,包括各步骤涉及的协议报文、函数实现和具体操作。文章还对 UPnP 协议的简介、特点、应用场景进行了分析,帮助读者理解其在网络设备互联互通中的实际应用价值。
下一篇文章将聚焦 TFTP 协议,解析其核心原理及在文件传输中的应用,同时讲解如何在W55MH32上实现 TFTP 功能,敬请期待!
WIZnet 是一家无晶圆厂半导体公司,成立于 1998 年。产品包括互联网处理器 iMCU™,它采用 TOE(TCP/IP 卸载引擎)技术,基于独特的专利全硬连线 TCP/IP。iMCU™ 面向各种应用中的嵌入式互联网设备。
WIZnet 在全球拥有 70 多家分销商,在香港、韩国、美国设有办事处,提供技术支持和产品营销。
香港办事处管理的区域包括:澳大利亚、印度、土耳其、亚洲(韩国和日本除外)。
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !