1 前言

        Modbus是一种串行通信协议,最初为工业自动化领域设计,因其简单、开放、易于实现而成为工业电子设备间通信的事实标准

        Modbus 的工作原理围绕着三个核心部分:主站(Master)、从站(Slave) 和 寄存器(Registers)。主站负责向特定从站发送数据请求或写入命令,从站则处理这些命令(读取或修改自身寄存器数据)并向主站返回响应。这种主站轮询、从站响应的同步模式,使其非常适合于工业设备的状态监控和控制。

        SMTP (Simple Mail Transfer Protocol) 是一种电子邮件传输协议,基于 TCP/IP,采用客户端-服务器模型和存储转发机制,专用于电子邮件的发送与传输。

        SMTP 的工作原理围绕着三个核心部分:发送方 SMTP 客户端、接收方 SMTP 服务器 和 命令/响应机制。发送方客户端连接服务器,通过文本命令(如 MAIL FROM, RCPT TO, DATA)指定发件人、收件人并传输邮件内容;接收方服务器处理命令、验证信息,并将邮件存入目标邮箱或转发。这种基于命令序列的模式,确保了电子邮件在互联网上的可靠传输

W5500io-M 是炜世推出的高性能SPI转以太网模块,具有以下特点:

  • 极简设计:集成MAC、PHY、32KB缓存及RJ45网口,通过4线SPI接口直连主控,3.3V供电,紧凑尺寸适配嵌入式场景 。
  • 简单易用:用户无需再移植复杂的TCP/IP协议栈到MCU中,可直接基于应用层数据做开发。
  • 资料丰富:提供丰富的MCU应用例程和硬件参考设计,可直接参考使用,大大缩短研发时间,硬件兼容W5100Sio-M模组,方便方案开发与迭代。
  • 应用广泛:在工业控制、智能电网、充电桩、安防消防、新能源、储能等领域都有广泛应用。

产品链接:商品详情

2 项目环境

2.1 硬件环境

  1. W5500io-M
  2. STM32F103VCT6 EVB
  3. 网络连接线
  4. 杜邦线若干
  5. 交换机或路由器

2.2 软件环境

  1. 邮箱
  2. Modbus Master Suite
  3. 飞思创串口助手
  4. 例程连接:https://www.w5500.com/w5500.html

3 硬件连接和方案

3.3 W5500硬件连接

//W5500_SCS	--->	STM32_GPIOD7	/*W5500的片选引脚*/
//W5500_SCLK	--->	STM32_GPIOB13	/*W5500的时钟引脚*/
//W5500_MISO	--->	STM32_GPIOB14	/*W5500的MISO引脚*/ 
//W5500_MOSI	--->	STM32_GPIOB15	/*W5500的MOSI引脚*/ 
//W5500_RESET	--->	STM32_GPIOD8	/*W5500的RESET引脚*/ 
//W5500_INT	--->	STM32_GPIOD9	/*W5500的INT引脚*/

3.4 方案图示

4 例程修改

        需要下载Modbus_TCP_ServerSMTP例程然后进行修改例程下载上方软件环境里面提供链接自行下载即可

        (1)下载好之后打开Modbus_TCP_Server例程然后SMTP例程中的dns.c dns.hsmtp.c smtp.h以及do_dns.cdo_dns.h文件加入Modbus_TCP_Server例程

        (2)文件路径进行添加

        (3)在user_main.h5添加一路SMTP需要使用socket

#ifndef __USER_MAIN_H__
#define __USER_MAIN_H__

#define SOCKET_ID 0
#define SMTP_SOCKET_ID 1
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)
/**
 * @brief   User Run Program
 * @param   none
 * @return  none
 */
void user_run(void);
#endif

        (4)打开user_main.cwhile1添加do_smtp函数并且SOCKET ID 替换为我们定义的SMTP SOCKET ID

  while (1)
  {
    do_Modbus(SOCKET_ID);
	
	do_smtp(SMTP_SOCKET_ID, ethernet_buf, smtp_server_ip);
	} 

        (5) 我们找到mb.c修改解析Modbus数据函数由于Modbus tcp不需要要crc校验所以3添加一个长度条件代码里添加一个解析单个寄存器else of函数添加21里面解析传回浓度如果浓度过高打印浓度此时Avalue已经赋值

void mbTCPtoEVB(uint8_t sn)
{
	if (MBtcp2evbFrame() != 0 && recv_len >= 12) // Frame received complete
	{
		if (pucASCIIBufferCur[0] == 0x01) // Check whether the device address is 0x01
		{
			if ((uint8_t)pucASCIIBufferCur[1] == 0x05) // Write to a single device
			{
				if ((uint8_t)pucASCIIBufferCur[4] == 0xff)
				{
					LED_ON();
					printf("LED ON\r\n");
				}
				else if ((uint8_t)pucASCIIBufferCur[4] == 0x00)
				{
					LED_OFF();
					printf("LED OFF\r\n");
				}
				send(sn, recv_data, recv_len);
			}
			else if((uint8_t)pucASCIIBufferCur[1] == 0x06)
			{
				Avalue = ((uint16_t)pucASCIIBufferCur[4] << 8) | pucASCIIBufferCur[5]; 
                if (Avalue > 30)
                {
                    printf("Alert: Concentration too high%u > 30\r\n", Avalue);		
                }
				send(sn, recv_data, recv_len);	
			}
			else if ((uint8_t)pucASCIIBufferCur[1] == 0x01) // Read Write to a single device
			{
				if (recv_data[recv_len - 1] != 0x01)
				{
					printf("len error!%x\r\n", recv_data[recv_len - 1]);
				}
				else
				{
					printf("Read OK!\r\n");
					send_data[0] = recv_data[0];
					send_data[1] = recv_data[1];
					send_data[2] = recv_data[2];
					send_data[3] = recv_data[3];
					send_data[4] = 0x00;
					send_data[5] = 0x04;
					send_data[6] = 0x01;
					send_data[7] = 0x01;
					send_data[8] = 0x01;
					send_data[9] = ~HAL_GPIO_ReadPin(LED_GPIO_Port, LED_Pin);
					send_len = 10;
					send(sn, send_data, send_len);
					memset(send_data, 0, send_len);
				}
			}
			}
			else
			{
				printf("error code!\r\n");
			}
		}
		else
		{
			printf("address error!\r\n");
		}
	}

        (6)把定义的Avaluemb.h11进行传出

#ifndef _MB_H_
#define _MB_H_

/**
 * @brief Parse Modbus TCP instructions and process them accordingly
 *
 * According to the given sequence number, the Modbus TCP data is converted into an EVB instruction and processed accordingly.
 *
 * @param sn socket number
 */
extern  uint16_t Avalue;
void mbTCPtoEVB(uint8_t sn);

#endif

        (7)此时回到while(1)加入条件判断然后执行发送信息操作

  while (1)
  {
    do_Modbus(SOCKET_ID);
		if(Avalue>30){ 
		do_smtp(SMTP_SOCKET_ID, ethernet_buf, smtp_server_ip);}
	} 

        (8)stmp.c修改接收邮件邮箱以及邮件主题内容15修改接收邮件邮箱账号713可以修改发件邮箱登录地址发件人邮箱10修改邮箱登录密码2122修改邮件主题内容系统中通过126邮箱服务器向指定QQ邮箱发送警报邮件。首先通过HELO命令建立连接,然后使用AUTH LOGIN进行Base64编码的用户名密码认证,接着设置发件人(MAIL FROM)和收件人(RCPT TO地址为232++++16@qq.com,再通过DATA命令传输邮件内容。邮件主题为"Breaking In",正文内容是"Excessive gas concentration",最后以\r\n.\r\n结束传输。

#define ETHERNET_MAX_BUF_SIZE (1024 * 2)
uint8_t Mail_Send_OK;
char hello[50] = "HELO localhost";                       // Identity command
char hello_reply[] = "250 OK";                           // Id successfully responded
char AUTH[50] = "AUTH LOGIN";                            // Authentication request
char AUTH_reply[] = "334 dXNlcm5hbWU6";                  // The authentication request was successfully sent
char name_126[100] = "wiznethk@126.com";                 // 126 Login email address
char base64name_126[200];                                // 126 base64 encoding of the login mailbox name
char name_reply[] = "334 UGFzc3dvcmQ6";                  // The login name was sent successfully
char password_126[50] = "ZPURADLGRUPQLVBK";              // 126 Email login password
char base64password_126[100];                            // base64 123 Password for logging in to the mailbox
char password_reply[] = "235 Authentication successful"; // Login successful response
char from[] = "wiznethk@126.com";                        // Sender email
char from_reply[] = "250 Mail OK";
char to[] = "232++++16@qq.com"; // Recipient email address
char to_reply[] = "250 Mail OK";·    
char data_init[10] = "data";                 // Request data transfer
char data_reply[] = "354";                   // The request was successfully responded to HEAD
char Cc[] = "";                              // Cc to email
char subject[] = "Breaking In";            // subject
char content[] = "Excessive gas concentration";            // text part
char mime_reply[] = "250 Mail OK queued as"; // The email was sent successfully
char mailfrom[50] = "MAIL FROM:<>";
char rcptto[50] = "rcpt to:<>";
char mime[200] = "From:\r\n";
char mime1[50] = "To:\r\n";
char mime2[50] = "Cc:\r\n";
char mime3[50] = "Subject:\r\n";
char mime4[50] = "MIME-Version:1.0\r\nContent-Type:text/plain\r\n\r\n";
char mime5[50] = "\r\n.\r\n";

        (9) stmp.c最后发送成功进入一个循环导致在这里出不去可以while(1)空循环去除然后在里面添加一个延时,邮件发送成功代表浓度已经超过标准如果不加延时持续发送警告信息请根据自己需要添加延时

  case SOCK_CLOSED:
    socket(sn, Sn_MR_TCP, anyport++, 0x00);
    break;
  default:
    break;
  }
	if(Mail_Send_OK)
	{
       // while(1){
		HAL_Delay(20000);
       // }
	}
}

5 配置连接

5.1 串口打印

5.2 Modbus Master Suite

        Modbus Master Suite是用于Modbus协议设备通信交互的工具。它支持多种Modbus协议,能通过串口或网络连接设备,可读写各类寄存器数据,具备功能码测试、数据转换与校验等功能。

        (1)首先需要先点击设置点击连接设置

        (2)选择Modbus TCP/IP连接模式地址和端口号填写上方串口打印点击确认就可以连接成功

        (3)连接成功串口显示连接信息

6 发送数据

        (1)接下来我们Modbus Master Suite点击功能然后点击寄存器

        (2)依次填写从机地址寄存器地址以及将要写入数值,然后点击发送

        (3)此时我们的值设置的小于30,打印出异常信息

        (4)再次进行配置此时我没增加到32然后点击发送

7 效果展示解析

        (1)此时,我们可以看到串口打印出浓度数值并且显示邮件发送成功

        (2)打开邮箱我们可以看到收到了气体浓度过高告警邮件

        (3)再来看一下刚才发的报文,由于两段报文差别不大我对浓度32进行解析

字节范围(十六进制)

字段名称

字段值(十六进制)

十进制值

含义说明

00 20

事务标识符(TID)

00 20

32

主站自定义的事务 ID(与报文 1 不同,说明是另一个独立请求)

00 00

协议标识符(PID)

00 00

0

固定为 0(Modbus 协议标识)

00 06

长度(LEN)

00 06

6

后续 PDU 字节数(单元标识符 + 功能码 + 数据 共 6 字节)

01

单元标识符(UID)

01

1

目标从站地址(仍为 1 ,与报文 1 操作同一从站)

06

功能码(FC)

06

6

写单个保持寄存器(Preset Single Register):修改 1 个保持寄存器的值

00 01

寄存器地址

00 01

1

要写入的保持寄存器起始地址(与报文 1 相同,操作同一寄存器地址)

00 20

写入值

00 20

32

要写入寄存器的数据(十进制 32 ,16 位无符号整数)

        功能总结:

        主站向 从站 ID = 1 的设备,发送指令:将地址为 1 的保持寄存器,写入值 32。

8 总结

        本系统通过 STM32 与 W5500 的结合,实现了 Modbus TCP 服务器的轻量化部署,同时通过 SMTP 服务打通了传感器数据与远程告警的链路。方案兼顾工业通信的稳定性与物联网应用的灵活性,可根据实际需求调整传感器阈值、邮件内容及硬件接口,适用于对实时性和可靠性有要求的分布式监测场景。感谢您的观看,如您对本文有不清楚的地方或想了解更多产品信息的,欢迎私信或在评论区留言,我们会及时回复您!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐