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

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

原创
作者头像
AnieaLanie
发布于 2021-12-25 14:21:42
发布于 2021-12-25 14:21:42
4.5K00
代码可运行
举报
文章被收录于专栏:铁子的专栏铁子的专栏
运行总次数:0
代码可运行

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
代码运行次数:0
运行
AI代码解释
复制
BYTE LRC (BYTE *nData, WORD wLength)
{
BYTE nLRC = 0 ; // LRC char initializedfor (int i = 0; i < wLength; i++)
nLRC += *nData++;return (BYTE)(-nLRC);} // End: LRC

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
#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
代码运行次数:0
运行
AI代码解释
复制
#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
代码运行次数:0
运行
AI代码解释
复制
#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
代码运行次数:0
运行
AI代码解释
复制
/* 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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
聊聊如何在Java应用中发送短信
很多业务场景里,我们都需要发送短信,比如登陆验证码、告警、营销通知、节日祝福等等。
勇哥java实战
2023/11/07
8350
个人开发者短信验证码接入方案技术分析与实践
在开发个人项目时,短信验证码是用户注册、登录验证等场景的必需功能。然而,传统短信服务提供商大多要求企业资质,给个人开发者造成了技术实现上的困难。本文将从技术角度分析这一问题,并分享一些实用的解决方案。
外滩首席运维
2025/06/09
970
一次短信验证码“撞库”,发生的惨案!!!
故事要从一天中午开始说起,同事小张正在午休,睡的正酣,突然被产品经理给叫醒。运营反馈,大量用户打客服电话,说到没有注册平台却收到成功注册平台账号的短信内容。
兔云小新LM
2021/12/27
2.6K0
一次短信验证码“撞库”,发生的惨案!!!
51单片机+SIM800C(GSM模块)实现短信发送功能
本项目利用51单片机和SIM800C GSM模块实现短信发送功能。短信作为一种广泛应用的通信方式,在许多领域具有重要的作用,如物联网、安防系统、远程监控等。通过将51单片机与SIM800C GSM模块相结合,可以实现在各种应用场景下的短信通信功能。
DS小龙哥
2023/11/08
1.4K0
51单片机+SIM800C(GSM模块)实现短信发送功能
thinkPHP框架实现的短信接口验证码功能示例
本文实例讲述了thinkPHP框架实现的短信接口验证码功能。分享给大家供大家参考,具体如下:
PHP开发工程师
2021/06/03
3.4K0
三个月写了个短信平台,开源出来!
大家好,我是勇哥。花了三个月的时间,我手写了个短信平台服务 platform-sms,今天开源出来 Beta 版本。
勇哥java实战
2023/11/26
1.6K1
Rocketmq--消息驱动
MQ(Message Queue)是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个先进先出的数
IT小马哥
2021/06/03
7260
Rocketmq--消息驱动
MySQL数据库基础练习系列48、短信发送系统
很多学生或者说是初学者在学习完成数据库的基础增删改查后就自认为在数据库这里就很熟悉了,但是不接触项目根本部知道需求,我这里准备了50个项目的基本需求来让大家来熟练各类项目的列信息,让大家更好的深入项目进行实战式的练习,可以让大家在后面面试的时候有更多更丰富的资历让大家可以与面试官侃侃而谈。
红目香薰
2024/06/16
1190
MySQL数据库基础练习系列48、短信发送系统
【愚公系列】2022年01月 Django商城项目08-注册界面-短信验证码
Celery由以下三部分构成:消息中间件(Broker)、任务执行单元Worker、结果存储(Backend)
愚公搬代码
2022/01/14
8750
【愚公系列】2022年01月 Django商城项目08-注册界面-短信验证码
【笔记整理】SpringBoot集成腾讯云短信
前言 记录一下最近使用SpringBoot基础腾讯云里的短信产品功能的体验。 1、腾讯云申请开通短信服务。 2、配置短信内容:分别创建签名、模板和群发短信。 3、使用SpringBoot工程集成测试。
pbinlog
2022/04/18
8.3K0
SpringBoot项目中快速集成腾讯云短信SDK实现手机验证码功能
大家春节好!我是程序员阿福,今天过年的日子祝大家在新的一年里健康平安、步步高升、虎年大吉大利、财源滚滚! 今天分享一篇简短一点的文章,希望在将来工作中需要的时候能够用得到,如果将来工作中需要实现短信验证码功能时可以打开我的公众号并翻到这篇文章再仔细参考我的实现思路,那么笔者分享这篇文章的用意也就达到了。
用户3587585
2022/03/09
4.6K1
SpringBoot项目中快速集成腾讯云短信SDK实现手机验证码功能
Python项目49-用户验证短信接口(可劲撸)
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/09/26
1.9K0
cat监控实现腾讯云短信告警
CAT(Central Application Tracking),是基于 Java 开发的分布式实时监控系统。CAT在基础存储、高性能通信、大规模在线访问、服务治理、实时监控、容器化及集群智能调度等领域提供业界领先的、统一的解决方案。CAT 目前在美团的产品定位是应用层的统一监控组件。
GavinWang
2020/05/28
10.9K0
Django实战-信息资讯-短信验证码
Django网络应用开发的5项基础核心技术包括模型(Model)的设计,URL 的设计与配置,View(视图)的编写,Template(模板)的设计和Form(表单)的使用。
小团子
2019/07/18
1.4K0
Django实战-信息资讯-短信验证码
rabbitMq实现系统内的短信发送设计&动态获取BEAN
1.短信非系统的重要节点操作,可以在任务完成之后,比如下单成功,发送下单成功的mq消息,短信服务接收到mq消息, 动态的判断该短信的code,通过全局公共的父类(调用中台等接口获取全部所有需要的对象参数),获取短信中的{mobile}等参数来替换短信模板中的可变量。 这样系统中的所有的发送短信,都可以继承该父类,获取参数,从而实现方便快捷的扩展短信接入和对原来的短信模板内容的修改或新增短信中的可变量。
oktokeep
2024/10/09
1740
rabbitMq实现系统内的短信发送设计&动态获取BEAN
了解短信的实现原理以及验证码短信API
短信作为一种便捷、快速的通信方式,已经在我们的日常生活中得到广泛应用。无论是个人通信、企业沟通还是身份验证等场景,短信都发挥着重要的作用。而实现短信功能的核心是短信实现原理和验证码短信API。
用户10428865
2023/07/11
1.8K0
了解短信的实现原理以及验证码短信API
Django项目第一天
1.在给用户授权的时候,用到了一个%,表示的是任何ip都可以连接这个数据库。换句话说,如果你换了电脑,你也是可以进行连接数据库继续开发的。
小闫同学啊
2019/07/18
7400
发送短信
发送短信也是项目中常见的功能,网站的注册码、验证码、营销信息基本上都是通过短信来发送给用户的。在下面的代码中我们使用了互亿无线短信平台(该平台为注册用户提供了50条免费短信以及常用开发语言发送短信的demo,可以登录该网站并在用户自服务页面中对短信进行配置)提供的API接口实现了发送短信的服务,当然国内的短信平台很多,读者可以根据自己的需要进行选择(通常会考虑费用预算、短信达到率、使用的难易程度等指标),如果需要在商业项目中使用短信服务建议购买短信平台提供的套餐服务。
用户8442333
2021/05/20
19.4K0
Python项目48-插播短信接口(使劲撸)
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/09/26
1.9K0
Python项目48-插播短信接口(使劲撸)
开源短信项目 platform-sms 发布了新版本 0.5.0
短信服务 platform-sms 0.5.0 发布 ,新的版本做了非常多的优化和改进。
勇哥java实战
2024/03/05
6150
相关推荐
聊聊如何在Java应用中发送短信
更多 >
LV.2
这个人很懒,什么都没有留下~
目录
  • 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 归档