前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >STM32 W5500 实现 TCP,DHCP 和 Web服务器

STM32 W5500 实现 TCP,DHCP 和 Web服务器

原创
作者头像
AnieaLanie
发布2021-12-25 22:21:42
2.8K0
发布2021-12-25 22:21:42
举报
文章被收录于专栏:铁子的专栏铁子的专栏

1. W5500 Modbus

1.1 Modbus协议简介

Modbus协议是一种消息结构,广泛用于建立智能设备之间的主从通信。从主站发送到从站的 Modbus消息包含从站地址、“命令”(例如“读寄存器”或“写寄存器”)、数据和校验和(LRC 或 CRC)。

由于 Modbus 协议只是一种消息传递结构,它独立于底层物理层。传统上使用 RS232、RS422 或 RS485 实现

Modbus比其他通信协议使用的更广泛的主要原因有:

  • 公开发表且无版权需求
  • 易于部署和维护
  • 对供应商来说,修改移动本地的比特或字节没有很多限制
1.2 Modbus的master/slave架构

Modbus是一个主/从 (master/slave) 架构的协议,一个节点是master节点,其它节点是slave节点。每一个slave节点都有一个唯一的地址。在串行和MB+网络中,只有被指定为master节点的节点可以启动一个命令。在以太网上,任何一个设备都能发送一个Modbus命令,但是通常也只有一个master节点设备启动指令。

1.3 Modbus命令

一个Modbus命令包括了打算执行的设备的Modbus地址。所有设备都会接收到命令,但是只有对应地址的设备会运行并回应命令 (地址0除外,指定地址0的命令是广播命令,所有接收到命令的设备都会运行,但是不进行回应)。所有的Modbus命令包含了检查码,以确定到达的命令没有被破坏。基本的Modbus命令能指令一个RTU ( 远程终端单元 ) 改变它的寄存器的某个值,控制或者读取一个I/O端口,以及指挥设备会送一个或者多个寄存器中的数据。

1.4 Modbus请求

请求中的功能码告诉被寻址的从设备要执行什么样的动作。数据字节包含从站执行该功能所需的任何附加信息。例如,功能码 03 将请求从机读取保持寄存器并响应其内容。数据字段必须包含告诉从机从哪个寄存器开始以及读取多少寄存器的信息。错误检查字段为从站提供了一种验证消息内容完整性的方法。

1.5 Modbus响应

如果从机做出正常响应,则响应中的功能码是请求中功能码的回显。数据字节包含从站收集的数据,例如寄存器值或状态。如果发生错误,则修改功能码以指示响应是错误响应,并且数据字节包含描述错误的代码。错误检查字段允许主机确认消息内容有效。

1.6 Modbus传输模式

在标准 Modbus 网络上进行通信时,控制器可以设置为使用两种传输模式之一:ASCII 或 RTU。

ASCII 模式 当控制器设置为使用 ASCII(美国信息交换标准代码)模式在 Modbus 网络上进行通信时,消息中的每个八位字节都作为两个 ASCII 字符发送。这种模式的主要优点是它允许在字符之间出现长达一秒的时间间隔而不会导致错误。

编码系统 十六进制 ASCII 可打印字符 0 ... 9, A ... F 每字节位数 1 个起始位 7 个数据位,最低有效位首先发送 1 位用于偶/奇奇偶校验 - 无位用于无奇偶校验 1 停止位如果奇偶校验如果没有奇偶校验 错误检查 纵向冗余校验 (LRC),则使用 2 位

ASCII帧

在 ASCII 模式下,消息以冒号 (:) 字符(ASCII 3A 十六进制)开始,并以回车换行 (CRLF) 对(ASCII 0D 和 0A 十六进制)结束。 为所有其他字段传输的允许字符为十六进制 0 ... 9、A ... F。联网设备持续监视网络总线以查找冒号字符。当收到一个时,每个设备都会对下一个字段(地址字段)进行解码,以确定它是否是被寻址的设备。 消息中的字符之间最多可以间隔一秒。如果出现更大的间隔,则接收设备假定发生了错误。一个典型的消息帧如下所示。:

RTU 模式 当控制器设置为使用 RTU(远程终端单元)模式在 Modbus 网络上进行通信时,消息中的每个八位字节包含两个四位十六进制字符。这种模式的主要优点是在相同的波特率下,其更大的字符密度允许比 ASCII 更好的数据吞吐量。每条消息都必须以连续的流传输。

编码系统 八位二进制、十六进制 0 ... 9、A ... F 消息的每个八位字段中包含的两个十六进制字符 每字节位数 1 起始位 8 数据位,最低有效位首先发送 1 位用于偶数/奇数奇偶校验 - 无奇偶校验 位 如果使用奇偶校验则为 1 个停止位 - 如果没有奇偶校验 错误校验字段 为 2 位 循环冗余校验 (CRC)

RTU 帧 在 RTU 模式下,消息以至少 3.5 个字符时间的静默间隔开始。这最容易实现为网络上正在使用的波特率的字符时间的倍数(在下图中显示为 T1-T2-T3-T4)。然后传输的第一个字段是设备地址。 为所有字段传输的允许字符为十六进制 0 ... 9、A ... F。联网设备持续监控网络总线,包括在静默间隔期间。当接收到第一个字段(地址字段)时,每个设备都会对其进行解码,以确定它是否是被寻址的设备。 在最后传输的字符之后,至少 3.5 个字符时间的类似间隔标志着消息的结束。在此间隔之后可以开始新消息。 整个消息帧必须作为连续流传输。如果在帧完成之前出现超过 1.5 个字符时间的静默间隔,接收设备将刷新未完成的消息并假定下一个字节将是新消息的地址字段。 类似地,如果新消息的开始时间早于前一条消息之后的 3.5 个字符时间,则接收设备会将其视为上一条消息的延续。这将设置一个错误,因为最终 CRC 字段中的值对于组合消息将无效。一个典型的消息帧如下所示。:

1.7 消息帧

地址字段 消息帧的地址字段包含两个字符 (ASCII) 或八位 (RTU)。各个从站设备分配的地址范围为 1 ... 247。

功能字段

功能代码字段告诉寻址的从站执行什么功能。 Modbus poll (Modbus主模拟器) 支持以下功能:

01 读取线圈状态 02 读取输入状态 03 读取保持寄存器 04 读取输入寄存器 05 写入单个线圈 06 写入单个寄存器 15 写入多个线圈 16 写入多个寄存器

数据字段包含请求或发送的数据。

错误检查字段的内容 标准 Modbus 网络使用两种错误检查方法。错误检查字段内容取决于正在使用的方法。

ASCII 当ASCII 模式用于字符组帧时,错误检查字段包含两个ASCII 字符。错误校验字符是对消息内容执行的纵向冗余校验 (LRC) 计算的结果,不包括起始冒号和终止 CRLF 字符。 LRC 字符作为 CRLF 字符之前的最后一个字段附加到消息中。

代码语言:javascript
复制
BYTE LRC (BYTE *nData, WORD wLength)
{
BYTE nLRC = 0 ; // LRC char initialized
​
for (int i = 0; i < wLength; i++)
nLRC += *nData++;
​
return (BYTE)(-nLRC);
​
} // End: LRC

RTU 当 RTU 模式用于字符帧时,错误检查字段包含一个 16 位值,实现为两个 8 位字节。错误校验值是对消息内容执行的循环冗余校验计算的结果。 CRC 字段作为消息中的最后一个字段附加到消息中。完成后,首先附加字段的低位字节,然后是高位字节。CRC 高位字节是消息中要发送的最后一个字节。

代码语言:javascript
复制
WORD CRC16 (const BYTE *nData, WORD wLength)
{
static const WORD wCRCTable[] = {
   0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
   0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
   0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
   0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
   0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
   0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
   0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
   0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
   0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
   0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
   0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
   0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
   0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
   0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
   0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
   0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
   0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
   0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
   0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
   0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
   0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
   0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
   0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
   0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
   0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
   0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
   0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
   0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
   0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
   0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
   0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
   0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };
​
BYTE nTemp;
WORD wCRCWord = 0xFFFF;
​
   while (wLength--)
   {
      nTemp = *nData++ ^ wCRCWord;
      wCRCWord >>= 8;
      wCRCWord  ^= wCRCTable[nTemp];
   }
   return wCRCWord;
} // End: CRC16
1.8 实验结果

2. DHCP

2.1 DHCP简介

DHCP(动态主机配置协议)是一个局域网的网络协议。指的是由服务器控制一段IP地址范围,客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。

2.2 DHCP功能
  1. 保证任何IP地址在同一时刻只能由一台DHCP客户机所使用。
  2. DHCP应当可以给用户分配永久固定的IP地址。
  3. DHCP应当可以同用其他方法获得IP地址的主机共存(如手工配置IP地址的主机)。
  4. DHCP服务器应当向现有的BOOTP客户端提供服务。
2.3 DHCP的分配方式

DHCP有三种机制分配IP地址:

  1. 自动分配方式(Automatic Allocation),DHCP服务器为主机指定一个永久性的IP地址,一旦DHCP客户端第一次成功从DHCP服务器端租用到IP地址后,就可以永久性的使用该地址。
  2. 动态分配方式(Dynamic Allocation),DHCP服务器给主机指定一个具有时间限制的IP地址,时间到期或主机明确表示放弃该地址时,该地址可以被其他主机使用。
  3. 工分配方式(Manual Allocation),客户端的IP地址是由网络管理员指定的,DHCP服务器只是将指定的IP地址告诉客户端主机。
2.4 DHCP工作原理

DHCP协议采用UDP作为传输协议,主机发送请求消息到DHCP服务器的67号端口,DHCP服务器回应应答消息给主机的68号端口。详细的交互过程如下图:

2.5 W5500+STM32F103实现DHCP代码

W5500作为DHCP客户端,路由器作为DHCP服务器端,连接上路由器后,路由器动态分配给W5500IP地址。

在DHCP请求的过程中,包括4个主要的阶段:发现阶段、提供阶段、选择阶段以及确认阶段。

首先W5500客户端发送DHCP DISCOVER消息(IP地址租用申请),这个消息通过广播方式发出,所有网络中的DHCP服务器都将接收到这个消息。随后,网络中的DHCP服务器会回应一个DHCPOFFER消息(IP地址租用提供),由于这个时候客户端还没有网络地址,所以DHCP OFFER也是通过广播的方式发送出去的。 后,向该服务器发送DHCP REQUEST消息。在DHCP REQUEST消息中将包含客户端然申请的IP地址。最后,DHCP服务器将回送DHCP ACK的响应消息来通知客户端可以使用该IP地址,该确认里面包含了分配的IP地址和该地址的一个稳定期限的租约(默认是8天),并同时更新DHCP数据库。

主要代码:

代码语言:javascript
复制
while(1)
{
​
 DHCP_run();
​
}
​
uint8_t DHCP_run(void)
{
​
  uint8_t  type;
​
  uint8_t  ret;
​
  if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;
​
  if(getSn_SR(SOCK_DHCP) != SOCK_UDP)
​
  socket(SOCK_DHCP, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);
​
  ret = DHCP_RUNNING;
​
  type = parseDHCPMSG();
​
  switch ( dhcp_state )
​
  {
​
      case STATE_DHCP_READY :
​
          DHCP_allocated_ip[0] = 0;
​
          DHCP_allocated_ip[1] = 0;
​
          DHCP_allocated_ip[2] = 0;
​
          DHCP_allocated_ip[3] = 0;   
​
          send_DHCP_DISCOVER();
​
          dhcp_time = 0;
​
          dhcp_state = STATE_DHCP_DISCOVER;
​
      break;
​
      case STATE_DHCP_DISCOVER :
​
          if (type == DHCP_OFFER)
​
          {
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Receive DHCP_OFFER\r\n");
​
              #endif
​
              DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
​
              DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
​
              DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
​
              DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3]; 
​
              send_DHCP_REQUEST();
​
              dhcp_time = 0;
​
              dhcp_state = STATE_DHCP_REQUEST;
​
          }
​
              else
​
              ret = check_DHCP_timeout();
​
      break;
​
      case STATE_DHCP_REQUEST :
​
          if (type == DHCP_ACK)
​
              {
​
                  #ifdef _DHCP_DEBUG_
​
                  printf("> Receive DHCP_ACK\r\n");
​
                  #endif
​
              if (check_DHCP_leasedIP())
​
              {
​
                  printf("ip:%d.%d.%d.%d\r\n",DHCP_allocated_ip[0],DHCP_allocated_ip[1],DHCP_allocated_ip[2],DHCP_allocated_ip[3]);
​
                  printf("sn:%d.%d.%d.%d\r\n",DHCP_allocated_sn[0],DHCP_allocated_sn[1],DHCP_allocated_sn[2],DHCP_allocated_sn[3]);
​
                  printf("gw:%d.%d.%d.%d\r\n",DHCP_allocated_gw[0],DHCP_allocated_gw[1],DHCP_allocated_gw[2],DHCP_allocated_gw[3]);
​
                  dhcp_ip_assign();
​
                  reset_DHCP_timeout();
​
                  hcp_state = STATE_DHCP_LEASED;
​
              }
​
              else
​
              {
​
                  reset_DHCP_timeout();
​
                  dhcp_ip_conflict();
​
                  dhcp_state = STATE_DHCP_READY;
​
              }
​
              }
​
              else if (type == DHCP_NAK)
​
              {
​
                  #ifdef _DHCP_DEBUG_
​
                  printf("> Receive DHCP_NACK\r\n");
​
                  #endif
​
                  reset_DHCP_timeout();
​
                  dhcp_state = STATE_DHCP_DISCOVER;
​
              }
​
              else
​
              ret = check_DHCP_timeout();
​
      break;
​
      case STATE_DHCP_LEASED :
​
          ret = DHCP_IP_LEASED;        
​
          if ((dhcp_lease_time != DEFAULT_LEASETIME) && ((dhcp_lease_time/2) < dhcp_time))
​
          {  
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Maintains the IP address \r\n");
​
              #endif
​
              type = 0;
​
              OLD_allocated_ip[0] = DHCP_allocated_ip[0];
​
              OLD_allocated_ip[1] = DHCP_allocated_ip[1];
​
              OLD_allocated_ip[2] = DHCP_allocated_ip[2];
​
              OLD_allocated_ip[3] = DHCP_allocated_ip[3];
​
              DHCP_XID++;
​
              send_DHCP_REQUEST();               
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_REREQUEST;
​
          }
​
      break;
​
      case STATE_DHCP_REREQUEST :
​
          ret = DHCP_IP_LEASED;
​
          if (type == DHCP_ACK)
​
          {
​
              dhcp_retry_count = 0;
​
              if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
​
              OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
​
              OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
​
              OLD_allocated_ip[3] != DHCP_allocated_ip[3])
​
          {
​
              ret = DHCP_IP_CHANGED;
​
              dhcp_ip_update();
​
              #ifdef _DHCP_DEBUG_
​
              printf(">IP changed.\r\n");
​
              #endif                 
​
          }
​
              #ifdef _DHCP_DEBUG_
​
              else printf(">IP is continued.\r\n");
​
              #endif                         
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_LEASED;
​
          }
​
          else if (type == DHCP_NAK)
​
          {
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
​
              #endif
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_DISCOVER;
​
          }
​
          else ret = check_DHCP_timeout();
​
      break;
​
      default :
​
      break;
​
      }
​
  return ret;
}
2.6 代码运行结果

本机连上路由器,本机IP地址如下:

将W5500+STM32连上路由器,由STM32串口输出查看W5500的IP地址如下:

本机 ping W5500结果:

连接成功。

3. W5500 TCP

3.1 本机建立TCP服务器

关闭本机网络,使用TCP测试工具建立TCP服务器

3.2 使用W5500,设置静态IP,建立TCP客户端

连接W5500到本机上,运行程序,启动TCP服务器,查看W5500客户端发送的数据

代码语言:javascript
复制
int main(void)
{
    System_Initialization();    //STM32系统初始化函数(初始化STM32时钟及外设)
    Load_Net_Parameters();      //装载网络参数    
    W5500_Hardware_Reset();     //硬件复位W5500
    W5500_Initialization();     //W5500初始货配置
    while (1)
    {
        W5500_Socket_Set();//W5500端口初始化配置
​
        if(W5500_Interrupt)//处理W5500中断      
        {
            W5500_Interrupt_Process();//W5500中断处理程序框架
        }
        if((S0_Data & S_RECEIVE) == S_RECEIVE)//如果Socket0接收到数据
        {
            S0_Data&=~S_RECEIVE;
            Process_Socket_Data(0);//W5500接收并发送接收到的数据
        }
        else if(W5500_Send_Delay_Counter >= 500)//定时发送字符串
        {
            if(S0_State == (S_INIT|S_CONN))
            {
                S0_Data&=~S_TRANSMITOK;
                memcpy(Tx_Buffer, "\r\nWelcome To NiRenElec!\r\n", 23); 
                Write_SOCK_Data_Buffer(0, Tx_Buffer, 23);//指定Socket(0~7)发送数据处理,端口0发送23字节数据
            }
            W5500_Send_Delay_Counter=0;
        }
    }
}
3.3 实验结果

4. 嵌入式Web服务器

4.1 接线部分

连接W5500和路由器,PC也连接到路由器上,便于访问网页。

4.2 代码部分

使用socket封装TCP协议,事项基于TCP的Socket通信

代码语言:javascript
复制
#include "socket.h"
#include "config.h"
#include "stdio.h"
#include "w5500.h"
#include "ult.h"
​
static uint16 local_port;
extern uint16 sent_ptr;
​
/**
@brief   This Socket function initialize the channel in perticular mode, and set the port and wait for W5200 done it.
@return  1 for sucess else 0.
*/
uint8 socket(SOCKET s, uint8 protocol, uint16 port, uint8 flag)
{
   uint8 ret;
   if (
        ((protocol&0x0F) == Sn_MR_TCP)    ||
        ((protocol&0x0F) == Sn_MR_UDP)    ||
        ((protocol&0x0F) == Sn_MR_IPRAW)  ||
        ((protocol&0x0F) == Sn_MR_MACRAW) ||
        ((protocol&0x0F) == Sn_MR_PPPOE)
      )
   {
      close(s);
      IINCHIP_WRITE(Sn_MR(s) ,protocol | flag);
      if (port != 0) {
         IINCHIP_WRITE( Sn_PORT0(s) ,(uint8)((port & 0xff00) >> 8));
         IINCHIP_WRITE( Sn_PORT1(s) ,(uint8)(port & 0x00ff));
      } else {
         local_port++; // if don't set the source port, set local_port number.
         IINCHIP_WRITE(Sn_PORT0(s) ,(uint8)((local_port & 0xff00) >> 8));
         IINCHIP_WRITE(Sn_PORT1(s) ,(uint8)(local_port & 0x00ff));
      }
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_OPEN); // run sockinit Sn_CR
​
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s)) )
         ;
      /* ------- */
      ret = 1;
   }
   else
   {
      ret = 0;
   }
   return ret;
}
​
​
/**
@brief   This function close the socket and parameter is "s" which represent the socket number
*/
void close(SOCKET s)
{
​
   IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_CLOSE);
​
   /* wait to process the command... */
   while( IINCHIP_READ(Sn_CR(s) ) )
      ;
   /* ------- */
        /* all clear */
   IINCHIP_WRITE( Sn_IR(s) , 0xFF);
}
​
​
/**
@brief   This function established  the connection for the channel in passive (server) mode. This function waits for the request from the peer.
@return  1 for success else 0.
*/
uint8 listen(SOCKET s)
{
   uint8 ret;
   if (IINCHIP_READ( Sn_SR(s) ) == SOCK_INIT)
   {
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_LISTEN);
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s) ) )
         ;
      /* ------- */
      ret = 1;
   }
   else
   {
      ret = 0;
   }
   return ret;
}
​
​
/**
@brief   This function established  the connection for the channel in Active (client) mode.
      This function waits for the untill the connection is established.
​
@return  1 for success else 0.
*/
uint8 connect(SOCKET s, uint8 * addr, uint16 port)
{
    uint8 ret;
    if
        (
            ((addr[0] == 0xFF) && (addr[1] == 0xFF) && (addr[2] == 0xFF) && (addr[3] == 0xFF)) ||
            ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) ||
            (port == 0x00)
        )
    {
      ret = 0;
    }
    else
    {
        ret = 1;
        // set destination IP
        IINCHIP_WRITE( Sn_DIPR0(s), addr[0]);
        IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
        IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
        IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
        IINCHIP_WRITE( Sn_DPORT0(s), (uint8)((port & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_DPORT1(s), (uint8)(port & 0x00ff));
        IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_CONNECT);
        /* wait for completion */
        while ( IINCHIP_READ(Sn_CR(s) ) ) ;
​
        while ( IINCHIP_READ(Sn_SR(s)) != SOCK_SYNSENT )
        {
            if(IINCHIP_READ(Sn_SR(s)) == SOCK_ESTABLISHED)
            {
                break;
            }
            if (getSn_IR(s) & Sn_IR_TIMEOUT)
            {
                IINCHIP_WRITE(Sn_IR(s), (Sn_IR_TIMEOUT));  // clear TIMEOUT Interrupt
                ret = 0;
                break;
            }
        }
    }
​
   return ret;
}
​
​
​
/**
@brief   This function used for disconnect the socket and parameter is "s" which represent the socket number
@return  1 for success else 0.
*/
void disconnect(SOCKET s)
{
   IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_DISCON);
​
   /* wait to process the command... */
   while( IINCHIP_READ(Sn_CR(s) ) )
      ;
   /* ------- */
}
​
​
/**
@brief   This function used to send the data in TCP mode
@return  1 for success else 0.
*/
uint16 send(SOCKET s, const uint8 * buf, uint16 len)
{
  uint8 status=0;
  uint16 ret=0;
  uint16 freesize=0;
​
  if (len > getIINCHIP_TxMAX(s)) ret = getIINCHIP_TxMAX(s); // check size not to exceed MAX size.
  else ret = len;
​
  // if freebuf is available, start.
  do
  {
    freesize = getSn_TX_FSR(s);
    status = IINCHIP_READ(Sn_SR(s));
    if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT))
    {
      ret = 0;
      break;
    }
  } while (freesize < ret);
​
​
  // copy data
  send_data_processing(s, (uint8 *)buf, ret);
  IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND);
​
  /* wait to process the command... */
  while( IINCHIP_READ(Sn_CR(s) ) );
​
  while ( (IINCHIP_READ(Sn_IR(s) ) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
  {
    status = IINCHIP_READ(Sn_SR(s));
    if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT) )
    {
      printf("SEND_OK Problem!!\r\n");
      close(s);
      return 0;
    }
  }
  IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
​
#ifdef __DEF_IINCHIP_INT__
   putISR(s, getISR(s) & (~Sn_IR_SEND_OK));
#else
   IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
#endif
​
   return ret;
}
​
​
​
/**
@brief   This function is an application I/F function which is used to receive the data in TCP mode.
      It continues to wait for data as much as the application wants to receive.
​
@return  received data size for success else -1.
*/
uint16 recv(SOCKET s, uint8 * buf, uint16 len)
{
   uint16 ret=0;
   if ( len > 0 )
   {
      recv_data_processing(s, buf, len);
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s) ));
      /* ------- */
      ret = len;
   }
   return ret;
}
​
​
/**
@brief   This function is an application I/F function which is used to send the data for other then TCP mode.
      Unlike TCP transmission, The peer's destination address and the port is needed.
​
@return  This function return send data size for success else -1.
*/
uint16 sendto(SOCKET s, const uint8 * buf, uint16 len, uint8 * addr, uint16 port)
{
   uint16 ret=0;
​
   if (len > getIINCHIP_TxMAX(s)) ret = getIINCHIP_TxMAX(s); // check size not to exceed MAX size.
   else ret = len;
​
   if( ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) || ((port == 0x00)) )//||(ret == 0) )
   {
      /* added return value */
      ret = 0;
   }
   else
   {
      IINCHIP_WRITE( Sn_DIPR0(s), addr[0]);
      IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
      IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
      IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
      IINCHIP_WRITE( Sn_DPORT0(s),(uint8)((port & 0xff00) >> 8));
      IINCHIP_WRITE( Sn_DPORT1(s),(uint8)(port & 0x00ff));
      // copy data
      send_data_processing(s, (uint8 *)buf, ret);
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND);
      /* wait to process the command... */
      while( IINCHIP_READ( Sn_CR(s) ) )
         ;
      /* ------- */
​
      while( (IINCHIP_READ( Sn_IR(s) ) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
      {
         if (IINCHIP_READ( Sn_IR(s) ) & Sn_IR_TIMEOUT)
         {
            /* clear interrupt */
            IINCHIP_WRITE( Sn_IR(s) , (Sn_IR_SEND_OK | Sn_IR_TIMEOUT)); /* clear SEND_OK & TIMEOUT */
            return 0;
         }
      }
      IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
   }
   return ret;
}
​
​
/**
@brief   This function is an application I/F function which is used to receive the data in other then
   TCP mode. This function is used to receive UDP, IP_RAW and MAC_RAW mode, and handle the header as well.
​
@return  This function return received data size for success else -1.
*/
uint16 recvfrom(SOCKET s, uint8 * buf, uint16 len, uint8 * addr, uint16 *port)
{
   uint8 head[8];
   uint16 data_len=0;
   uint16 ptr=0;
   uint32 addrbsb =0;
   if ( len > 0 )
   {
      ptr     = IINCHIP_READ(Sn_RX_RD0(s) );
      ptr     = ((ptr & 0x00ff) << 8) + IINCHIP_READ(Sn_RX_RD1(s));
      addrbsb = (uint32)(ptr<<8) +  (s<<5) + 0x18;
      
      switch (IINCHIP_READ(Sn_MR(s) ) & 0x07)
      {
      case Sn_MR_UDP :
        wiz_read_buf(addrbsb, head, 0x08);        
        ptr += 8;
        // read peer's IP address, port number.
        addr[0]  = head[0];
        addr[1]  = head[1];
        addr[2]  = head[2];
        addr[3]  = head[3];
        *port    = head[4];
        *port    = (*port << 8) + head[5];
        data_len = head[6];
        data_len = (data_len << 8) + head[7];
​
        addrbsb = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);                
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      case Sn_MR_IPRAW :
        wiz_read_buf(addrbsb, head, 0x06);        
        ptr += 6;
        addr[0]  = head[0];
        addr[1]  = head[1];
        addr[2]  = head[2];
        addr[3]  = head[3];
        data_len = head[4];
        data_len = (data_len << 8) + head[5];
​
        addrbsb  = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);        
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      case Sn_MR_MACRAW :
        wiz_read_buf(addrbsb, head, 0x02);
        ptr+=2;
        data_len = head[0];
        data_len = (data_len<<8) + head[1] - 2;
        if(data_len > 1514)
        {
           printf("data_len over 1514\r\n");
           while(1);
        }
​
        addrbsb  = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      default :
            break;
      }
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
​
      /* wait to process the command... */
      while( IINCHIP_READ( Sn_CR(s)) ) ;
      /* ------- */
   }
   return data_len;
}
​
#ifdef __MACRAW__
void macraw_open(void)
{
  uint8 sock_num;
  uint16 dummyPort = 0;
  uint8 mFlag = 0;
  sock_num = 0;
​
​
  close(sock_num); // Close the 0-th socket
  socket(sock_num, Sn_MR_MACRAW, dummyPort,mFlag);  // OPen the 0-th socket with MACRAW mode
}
​
​
uint16 macraw_send( const uint8 * buf, uint16 len )
{
   uint16 ret=0;
   uint8 sock_num;
   sock_num =0;
​
​
   if (len > getIINCHIP_TxMAX(sock_num)) ret = getIINCHIP_TxMAX(sock_num); // check size not to exceed MAX size.
   else ret = len;
​
   send_data_processing(sock_num, (uint8 *)buf, len);
​
   //W5500 SEND COMMAND
   IINCHIP_WRITE(Sn_CR(sock_num),Sn_CR_SEND);
   while( IINCHIP_READ(Sn_CR(sock_num)) );
   while ( (IINCHIP_READ(Sn_IR(sock_num)) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK );
   IINCHIP_WRITE(Sn_IR(sock_num), Sn_IR_SEND_OK);
​
   return ret;
}
​
uint16 macraw_recv( uint8 * buf, uint16 len )
{
   uint8 sock_num;
   uint16 data_len=0;
   uint16 dummyPort = 0;
   uint16 ptr = 0;
   uint8 mFlag = 0;
   sock_num = 0;
​
   if ( len > 0 )
   {
​
      data_len = 0;
​
      ptr = IINCHIP_READ(Sn_RX_RD0(sock_num));
      ptr = (uint16)((ptr & 0x00ff) << 8) + IINCHIP_READ( Sn_RX_RD1(sock_num) );
      //-- read_data(s, (uint8 *)ptr, data, len); // read data
      data_len = IINCHIP_READ_RXBUF(0, ptr);
      ptr++;
      data_len = ((data_len<<8) + IINCHIP_READ_RXBUF(0, ptr)) - 2;
      ptr++;
​
      if(data_len > 1514)
      {
         printf("data_len over 1514\r\n");
         printf("\r\nptr: %X, data_len: %X", ptr, data_len);
         //while(1);
         /** recommand : close and open **/
         close(sock_num); // Close the 0-th socket
         socket(sock_num, Sn_MR_MACRAW, dummyPort,mFlag);  // OPen the 0-th socket with MACRAW mode
         return 0;
      }
​
      IINCHIP_READ_RXBUF_BURST(sock_num, ptr, data_len, (uint8*)(buf));
      ptr += data_len;
​
      IINCHIP_WRITE(Sn_RX_RD0(sock_num),(uint8)((ptr & 0xff00) >> 8));
      IINCHIP_WRITE(Sn_RX_RD1(sock_num),(uint8)(ptr & 0x00ff));
      IINCHIP_WRITE(Sn_CR(sock_num), Sn_CR_RECV);
      while( IINCHIP_READ(Sn_CR(sock_num)) ) ;
   }
​
   return data_len;
}
#endif
​

使用SPI2连接W5500和STM32

代码语言:javascript
复制
#include "stm32f10x.h"
#include "config.h"
#include "socket.h"
#include "w5500.h"
#include "ult.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
​
void WIZ_SPI_Init(void)
{
    SPI_InitTypeDef   SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO , ENABLE);  
  // Port B output
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_SetBits(GPIOB, GPIO_Pin_12);
  /* Configure SPIy pins: SCK, MISO and MOSI */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13| GPIO_Pin_14| GPIO_Pin_15;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
      /* SPI Config -------------------------------------------------------------*/
      SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
      SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
      SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
      SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
      SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
      SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
      SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
      SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
      SPI_InitStructure.SPI_CRCPolynomial = 7;
​
      SPI_Init(SPI2, &SPI_InitStructure);
      SPI_Cmd(SPI2, ENABLE);
}
​
// Connected to Data Flash
void WIZ_CS(uint8_t val)
{
    if (val == LOW) 
    {
        GPIO_ResetBits(GPIOB, WIZ_SCS); 
    }
    else if (val == HIGH)
    {
        GPIO_SetBits(GPIOB, WIZ_SCS); 
    }
}
​
uint8_t SPI2_SendByte(uint8_t byte)
{
      while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
         
      SPI_I2S_SendData(SPI2, byte);
          
      while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
          
      return SPI_I2S_ReceiveData(SPI2);
}

Web HTTP部分,将html内容写到代码中,在配合http协议的状态码实现网页

代码语言:javascript
复制
#ifndef __WEBPAGE_H
#define __WEBPAGE_H
#define INDEX_HTML  "<!DOCTYPE html>"\
"<html>"\
"<head>"\
"<title>Ç峿µÄÍøÒ³ÅäÖÃ</title>"\
"<meta http-equiv='Content-Type' content='text/html; charset=GB2312'/>"\
"<style type='text/css'>"\
"body {text-align:left; background-color:#c0deed;font-family:Verdana;}"\
"#main {margin-right:auto;margin-left:auto;margin-top:30px;}"\
"label{display:inline-block;width:150px;}"\
"#main h3{color:#66b3ff; text-decoration:underline;}"\
"</style>"\
"<script>"\
"function $(id) { return document.getElementById(id); };"\
"function settingsCallback(o) {"\
"if ($('txtVer')) $('txtVer').value = o.ver;"\
"if ($('txtMac')) $('txtMac').value = o.mac;"\
"if ($('txtIp')) $('txtIp').value = o.ip;"\
"if ($('txtSub')) $('txtSub').value = o.sub;"\
"if ($('txtGw')) $('txtGw').value = o.gw;"\
\
"if ($('txtCode')) $('txtCode').value = o.Code;"\
"if ($('txtDTMB_Freq')) $('txtDTMB_Freq').value = o.DTMB_Freq;"\
"if ($('txtFM_Freq')) $('txtFM_Freq').value = o.FM_Freq;"\
"if ($('txtSx_Freq')) $('txtSx_Freq').value = o.Sx_Freq;"\
\
"};"\
"</script>"\
"</head>"\
"<body>"\
"<div id='main'>"\
"<div style='background:snow; display:block;padding:10px 20px;'>"\
"<h3 align='center'>²é¿´ÍøÂç²ÎÊý</h3>"\
"<form id='frmSetting' method='POST' action='config.cgi'>"\
"<p><label for='txtIp'>¹Ì¼þ°æ±¾ºÅ:</label><input type='text' id='txtVer' name='ver' size='16' disabled='disabled' /></p>"\
"<p><label for='txtIp'>MACµØÖ·:</label><input type='text' id='txtMac' name='mac' size='16' disabled='disabled' /></p>"\
"<p><label for='txtIp'>IPµØÖ·:</label><input type='text' id='txtIp' name='ip' size='16' disabled='disabled'/></p>"\
"<p><label for='txtSub'>×ÓÍøÑÚÂë:</label><input type='text' id='txtSub' name='sub' size='16' disabled='disabled'/></p>"\
"<p><label for='txtGw'>ĬÈÏÍø¹Ø:</label><input type='text' id='txtGw' name='gw' size='16' disabled='disabled'/></p>"\
\
"<p><label for='txtCode'>ÐÐÕþ±àÂë:</label><input type='text' id='txtCode' name='Code' size='16' disabled='disabled'/></p>"\
"<p><label for='txtDTMB_Freq'>DTMBƵÂÊ:</label><input type='text' id='txtDTMB_Freq' name='DTMB_Freq' size='16' disabled='disabled' /></p>"\
"<p><label for='txtFM_Freq'>µ÷ƵƵÂÊ:</label><input type='text' id='txtFM_Freq' name='FM_Freq' size='16' disabled='disabled'/></p>"\
"<p><label for='txtSx_Freq'>ÎÞÏßƵÂÊ:</label><input type='text' id='txtSx_Freq' name='Sx_Freq' size='16' disabled='disabled' /></p>"\
"<p style='color:DarkBlue'><label for='txtPassword'>µÇ¼ÃÜÂë6λ:</label><input type='password' id='txtPassword' name='Password' size='16' enabled='enabled'/></p>"\
"<input type='submit' value=' µÇ  ¼ ' /></p>"\
\
"</form>"\
"</div>"\
"</div>"\
"<div style='margin:5px 5px;'>"\
"&copy;Copyright 2017 by Ç峿"\
"</div>"\
"<script type='text/javascript' src='w5500.js'></script>"\
"</body>"\
"</html>"
​
#endif
代码语言:javascript
复制
/* HTML Doc. for ERROR */
#define ERROR_HTML_PAGE "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 78\r\n\r\n<HTML>\r\n<BODY>\r\nSorry, the page you requested was not found.\r\n</BODY>\r\n</HTML>\r\n\0"
//static char  ERROR_HTML_PAGE[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 78\r\n\r\n<HTML>\r\n<BODY>\r\nSorry, the page you requested was not found.\r\n</BODY>\r\n</HTML>\r\n\0";
​
#define ERROR_REQUEST_PAGE "HTTP/1.1 400 OK\r\nContent-Type: text/html\r\nContent-Length: 50\r\n\r\n<HTML>\r\n<BODY>\r\nInvalid request.\r\n</BODY>\r\n</HTML>\r\n\0"
//static char ERROR_REQUEST_PAGE[] = "HTTP/1.1 400 OK\r\nContent-Type: text/html\r\nContent-Length: 50\r\n\r\n<HTML>\r\n<BODY>\r\nInvalid request.\r\n</BODY>\r\n</HTML>\r\n\0";
​
#define RETURN_CGI_PAGE "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 59\r\n\r\n<html><head><title>iWeb - Configuration</title></head><BODY>CGI command was executed.</BODY></HTML>\0"
​
​
/* Response header for HTML*/
#define RES_HTMLHEAD_OK "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: "
//static PROGMEM char RES_HTMLHEAD_OK[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: ";
/* Response head for TEXT */
#define RES_TEXTHEAD_OK "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: "
​
/* Response head for GIF */
#define RES_GIFHEAD_OK  "HTTP/1.1 200 OK\r\nContent-Type: image/gif\r\nContent-Length: "
​
/* Response head for JPEG */
#define RES_JPEGHEAD_OK "HTTP/1.1 200 OK\r\nContent-Type: image/jpeg\r\nContent-Length: "       
​
/* Response head for FLASH */
#define RES_FLASHHEAD_OK "HTTP/1.1 200 OK\r\nContent-Type: application/x-shockwave-flash\r\nContent-Length: "
//static PROGMEM char RES_FLASHHEAD_OK[] = "HTTP/1.1 200 OK\r\nContent-Type: application/x-shockwave-flash\r\nContent-Length: ";
​
/* Response head for MPEG */
#define RES_MPEGHEAD_OK "HTTP/1.1 200 OK\r\nContent-Type: video/mpeg\r\nContent-Length: "   
​
/* Response head for PDF */
#define RES_PDFHEAD_OK "HTTP/1.1 200 OK\r\nContent-Type: application/pdf\r\nContent-Length: "
​
//digital I/O out put control result response
#define DOUT_RES_1  "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n1"
#define DOUT_RES_0  "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n0"
4.3 实验结果

5. 参考

[1] 从路由器获取动态IP地址

[2] DHCP

[3] STM32 移植FreeModbus详细过程

[4] Modbus通讯协议(二)——RTU

[5] STM32F103+W5500做的web服务

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.1 Modbus协议简介
  • 1.2 Modbus的master/slave架构
  • 1.3 Modbus命令
  • 1.4 Modbus请求
  • 1.5 Modbus响应
  • 1.6 Modbus传输模式
  • 1.7 消息帧
  • 1.8 实验结果
  • 2. DHCP
    • 2.1 DHCP简介
      • 2.2 DHCP功能
        • 2.3 DHCP的分配方式
          • 2.4 DHCP工作原理
            • 2.5 W5500+STM32F103实现DHCP代码
              • 2.6 代码运行结果
              • 3. W5500 TCP
                • 3.1 本机建立TCP服务器
                  • 3.2 使用W5500,设置静态IP,建立TCP客户端
                    • 3.3 实验结果
                    • 4. 嵌入式Web服务器
                      • 4.1 接线部分
                        • 4.2 代码部分
                          • 4.3 实验结果
                          • 5. 参考
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档