(Arduino)使用ESP32 + W5500+AIR780E实现 Ethernet Modbus转Mqtt

(Arduino)使用ESP32 + W5500+AIR780E实现 Ethernet Modbus转Mqtt

实现Ethernet Modbus Tcp 与外设通讯上传至物联网服务器。
提示:本项目的开发只是应用集成。


前言

工业领域的信息化越来越普遍,本项目适用于支持Modbus Tcp的外设,通过4G实现物联网需求。

一、硬件配置和环境

硬件配置
主控MCU: ESP32-WROOM-32
Ethernet 模块: W5500
4G模块: Air780E
电源模块: 输入:4.7-40 VDC 输出 5V

开发环境
Aruino 2.1.1
MQTT服务器: Moisquito
Modbus Tcp Slave 模拟器。

二、开发步骤

1.搭建Mqtt服务器

可以参考文章
MQTT服务器 Mosquitto的部署和应用

2.将ESP32和AIR780E连接

可以参考如下连接http://wiki.waaax.cn/MODULE/GSM/AIR780EX/AIR780EX.html
ESP32和Air780串口通讯。(如何需要用电脑监控该串口指令,需要TTL模块)
在这里插入图片描述
VIN ->5V DC
V_MCU->3,3VDC
GND -> GND
TXD -> P16(UTRA2_RX)
RXD->P17(UTRA2_TX
右边 PWK 和GND 短接

3.将ESP32和W5500连接

SPI接口连接
在这里插入图片描述
在这里插入图片描述
ESP32 对应接口如下:

ESP32_MOSI 23
ESP32_MISO 19
ESP32_SCK 18
ESP32_CS 5

4.代码实现

项目中所应用第三方库 modbus-esp8266,感谢大神提供
https://github.com/emelianov/modbus-esp8266

1.初始化

IPAddress mdIp;  // Address of Modbus Slave device
//ModbusIP mb;
ModbusEthernet mb;
//初始化 modbus tcp
void initMbTcpMaster() {
  Serial.print("Init Modbus Tcp:");
  mb.client();
  if (String(mb_slave_ip).indexOf("*") < 0) {
    Serial.println(mb_slave_ip);
    mdIp.fromString(mb_slave_ip);
  } else {
    Serial.println("192.168.1.254");
    mdIp.fromString("192.168.1.254");
  }
}
//初始化ETHERNET
void initEthernet() {
  Serial.print("Initialize Ethernet:");
  Ethernet.init(ESP32_CS);  // CS pin 5
  if (Ethernet.begin(mac,10000,4000) == 0) {
    // // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
      return;
    }
    if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }

    if (String(ethernet_ip).indexOf("*") < 0) {
      IPAddress ethernetIp;
      ethernetIp.fromString(ethernet_ip);
      Ethernet.begin(mac, ethernetIp);  // use DHCP
    }
  }
  // start the server
  Serial.println(Ethernet.localIP());
}
// 初始化4G模块
bool init780E() {
  Serial.println("init Air780E:");
  // 确认模块是否通讯上
  if (sendCommand("AT\r\n", "OK\r\n", 3000, 10) != Success){
    // Handle AT
		delay(500);
    return false;
	}
  if (sendCommand("AT\r\n", "OK\r\n", 3000, 10) == Success);
	else { errorLog(1); return false;}
  // 恢复出厂设置
	if (sendCommand("AT&F\r\n", "OK\r\n", 3000, 10) == Success);
	else {errorLog(2);}
  // 检查网络信号强度
	if (sendCommand("AT+CSQ\r\n", "OK\r\n", 3000, 10) == Success);
	else {errorLog(3);  return false;}
	delay(100);
  // 检查sim卡是否被识别
	if (sendCommandReceive2Keyword("AT+CPIN?\r\n", "READY","OK\r\n" ,3000, 10) == Success);
	else {errorLog(4);  return false;}
	delay(100);
  // 查询运营商
	if (sendCommand("AT+COPS?\r\n", "OK\r\n", 3000, 10) == Success);
	else {errorLog(5);  return false;}
	delay(100);
	if (sendCommandReceive2Keyword("AT+CREG?\r\n", ",1","OK\r\n", 3000, 10) == Success);	//本地SIM卡
	else if(sendCommandReceive2Keyword("AT+CREG?\r\n", ",5", "OK\r\n",3000, 10) == Success	);//漫游SIM卡
	else	{errorLog(6);  return false;}
  // 链接断链后重连
	if (sendCommand("AT+CIPSHUT\r\n", "OK\r\n", 5000, 10) == Success);
	else {errorLog(7);  return false;}
  // 查询网络是否激活
	if (sendCommandReceive2Keyword("AT+CGATT?\r\n", "+CGATT: 1","OK\r\n" ,3000, 10) == Success);
	else {errorLog(8);  return false;}
	delay(100);
	if (sendCommand("AT+CSTT\r\n", "OK\r\n", 3000, 10) == Success);
	else {errorLog(9);  return false;}
	delay(100);
	if (sendCommand("AT+CIICR\r\n", "OK\r\n", 3000, 10) == Success);
	else {errorLog(10);  return false;}
	delay(100);
	if (sendCommandReceive2Keyword("AT+CIFSR\r\n", ".","\r\n" ,3000, 10) == Success);
	else {errorLog(11);  return false;}
	delay(100);
  Serial.println(" init Air780E: OK");
  return true;
}

2.读取Modbus Tcp寄存器

// 读取modbus tcp数据
// mb_hold_regs 是开放变量用于设置读取的寄存器地址例如:1,2,3,4, 就是读取1、2、3、4号寄存器地址的内容
String mdRead() {
  String result = "";
  uint16_t res[225];
  int in = 0;
  if (mb.isConnected(mdIp)) {  // Check if connection to Modbus Slave is established
    String token = String(mb_hold_regs);
    while (true) {  // 循环遍历每个子串
      int idx = token.indexOf(",");
      if (idx >= 0) {
        uint16_t ret = 0;
        uint16_t addr = token.substring(0, idx).toInt();
        ret = mb.readHreg(mdIp, addr, &res[in], 1, nullptr, mb_slave_id);  // Initiate Read Coil from Modbus Slave
        mb.task();
        token = token.substring(idx + 1, token.length());
        in++;
      } else
        break;
    }
  } else {
    mb.connect(mdIp, 502);                // Try to connect if no connection
    Serial.println("modbus connecting");  // Common local Modbus task
    mbInit = true;
    delay(5000);
    return "";
  }  // Common local Modbus task
  mb.task();
  if(mbInit){
    mbInit = false;
    return "";
  }
  for (int i = 0; i < in; i++)
    result += (String(res[i]) + ",");
  if (result != "")
    Serial.printf("modebus read:%s\n", result.c_str());
  return result;
}

3.发送MQTT


void mqttSend4G(String msg) {
  if (msg == "") return;
  if (mqttConnected == false) {
    Serial.printf("Mqtt server connect %s\n", mqtt_server);
    connectMQTTServer();
  }
  if (mqttConnected) {
    Serial.printf("send mqtt message topic: %s,value: %s\n", mqttTopic.c_str(), msg.c_str());
    publishDataToServer(msg);
  }
}

void publishDataToServer(String msg) {
	char send_buf[150] = {0};
	char tmp[100] = {0};
	char value_str[15] = {0};
	memset(send_buf, 0, 150);    //清空
	memset(tmp, 0, 100);    //清空
	strcat(send_buf, "AT+MPUB=\"");
	strcat(send_buf, mqttTopic.c_str());
	strcat(send_buf, "\", 0, 0,\"");
	strcat(send_buf, msg.c_str());
	strcat(send_buf, "\"\r\n"); 
	if (sendCommand(send_buf ,"OK\r\n",3000, 10) == Success);
	else errorLog(1);
}

void connectMQTTServer() {
  if(!init780E()) return;
	char send_buf[100] = {0};
	memset(send_buf, 0, 100);    //清空
	strcpy(send_buf, "AT+MCONFIG=\"");
	strcat(send_buf, serial);
	strcat(send_buf, "\",\"");
	strcat(send_buf, mqtt_user);
	strcat(send_buf, "\",\"");
	strcat(send_buf, mqtt_password);
	strcat(send_buf, "\"\r\n");

	if (sendCommand(send_buf, "OK\r\n", 8000, 5) == Success);
	else {errorLog(12); return;}
	memset(send_buf, 0, 100);    //清空
	strcpy(send_buf, "AT+MIPSTART=\"");
	strcat(send_buf, mqtt_server);
	strcat(send_buf, "\",\"");
	strcat(send_buf, String(portNumber).c_str());
	strcat(send_buf, "\"\r\n");
	if (sendCommand(send_buf, "OK\r\n", 8000, 5) == Success);
	else {errorLog(13);return;}
	if (sendCommand("AT+MCONNECT=0,80\r\n", "ALREADY CONNECT", 10000, 1) == Success);
	else {
    errorLog(14);
    if (sendCommand("AT+MCONNECT=0,80\r\n", "OK\r\n", 10000, 1) != Success){
      errorLog(15);
      return;
    }
  }
  mqttConnected = true;
	delay(100);
}

void(* resetFunc) (void) = 0; //制造重启命令

void errorLog(int num) {
	Serial.print("ERROR");
	Serial.println(num);
  mqttConnected = false;
}

unsigned int sendCommand(char *Command, char *Response, unsigned long Timeout, unsigned char Retry){
	clrair780RxBuffer();
	for (unsigned char n = 0; n < Retry; n++){
		Serial.print("send AT Command:");
		Serial.println(Command);
		Serial2.print(Command);
		Time_Cont = 0;
		while (Time_Cont++ < Timeout)
		{
      delay(1);
			air780ReadBuffer();
			// Serial.print(".");	//1.8.x的IDE不加这个会有bug
			if (strstr(air780RxBuffer, Response) != NULL){
				Serial.print("receive AT Command:");
				Serial.println(air780RxBuffer); //输出接收到的信息
				clrair780RxBuffer();
				return Success;
			}
			
		}
		Time_Cont = 0;
	}
	Serial.print("receive AT Command:");
	Serial.println(air780RxBuffer);//输出接收到的信息
	clrair780RxBuffer();
	return Failure;
}

unsigned int sendCommandReceive2Keyword(char *Command, char *Response, char *Response2, unsigned long Timeout, unsigned char Retry) {
	clrair780RxBuffer();
	for (unsigned char n = 0; n < Retry; n++)
	{
		Serial.print("send AT Command:");
		Serial.println(Command);
		Serial2.write(Command);
		Time_Cont = 0;	
		while (Time_Cont++ < Timeout)
		{
      delay(1);
			air780ReadBuffer();
			// Serial.print(".");	//1.8.x的IDE不加这个会有bug
			if (strstr(air780RxBuffer, Response) != NULL && strstr(air780RxBuffer, Response2) != NULL){
				Serial.print("receive AT Command:");
				Serial.println(air780RxBuffer); //输出接收到的信息
				clrair780RxBuffer();
				return Success;
			}
		}
		Time_Cont = 0;
	}
	Serial.print("receive AT Command:");
	Serial.println(air780RxBuffer);//输出接收到的信息
	clrair780RxBuffer();
	return Failure;
}

void air780ReadBuffer() {
	while (Serial2.available())
	{
		air780RxBuffer[air780BufferCount++] = Serial2.read();
		if (air780BufferCount == AirRxBufferLength)clrair780RxBuffer();
	}
}

void clrair780RxBuffer(void) {
	memset(air780RxBuffer, 0, AirRxBufferLength);      //清空
	air780BufferCount = 0;
}

效果如下

串口打印出发送信息
在这里插入图片描述
MQTT.FX监控发布内容
在这里插入图片描述

总结

调试4G模块时,使用AT指令时,可能会又报警信息,附上AT 错误列表如下。
摘抄自https://momoiot.co.kr/catm-at/cmd/cme-error-codes/

Final result code +CME ERROR: indicates an error related to mobile equipment or network.
values used by common messaging commands:
Code of Meaning
0 phone failure
1 no connection to phone
2 phone-adaptor link reserved
3 operation not allowed
4 operation not supported
5 PH-SIM PIN required
6 PH-FSIM PIN required
7 PH-FSIM PUK required
10 SIM not inserted
11 SIM PIN required
12 SIM PUK required
13 SIM failure
14 SIM busy
15 SIM wrong
16 incorrect password
17 SIM PIN2 required
18 SIM PUK2 required
20 memory full
21 invalid index
22 not found
23 memory failure
24 text string too long
25 invalid characters in text string
26 dial string too long
27 invalid characters in dial string
30 no network service
31 network timeout
32 network not allowed – emergency call only
40 network personalization PIN required
41 network personalization PUK required
42 network subset personalization PIN required
43 network subset personalization PUK required
44 service provider personalization PIN required
45 service provider personalization PUK required
46 corporate personalization PIN required
47 corporate personalization PUK required
99 resource limitation
100 unknown
103 Illegal MS
106 Illegal ME
107 GPRS services not allowed
111 PLMN not allowed
112 Location area not allowed
113 Roaming not allowed in this location area
132 service option not supported
133 requested service option not subscribed
134 service option temporarily out of order
148 unspecified GPRS error
149 PDP authentication failure
150 invalid mobile class
160 DNS resolve failed
161 Socket open failed
171 MMS task is busy now
172 The MMS data is oversize
173 The operation is overtime
174 There is no MMS receiver
175 The storage for address is full
176 Not find the address
177 The connection to network is failed
178 Failed to read push message
179 This is not a push message
180 gprs is not attached
181 tcpip stack is busy
182 The MMS storage is full
183 The box is empty
184 failed to save MMS
185 It is in edit mode
186 It is not in edit mode
187 No content in the buffer
188 Not find the file
189 Failed to receive MMS
190 Failed to read MMS
191 Not M-Notification.ind
192 The MMS enclosure is full
193 Unknown
600 No Error
601 Unrecognized Command
602 Return Value Error
603 Syntax Error
604 Unspecified Error
605 Data Transfer Already
606 Action Already
607 Not At Cmd
608 Multi Cmd too long
609 Abort Cops
610 No Call Disc
611 BT SAP Undefined
612 BT SAP Not Accessible
613 BT SAP Card Removed
614 AT Not Allowed By Customer
753 missing required cmd parameter
754 invalid SIM command
755 invalid File Id
756 missing required P1/2/3 parameter
757 invalid P1/2/3 parameter
758 missing required command data
759 invalid characters in command data
765 Invalid input value
766 Unsupported mode
767 Operation failed
768 Mux already running
769 Unable to get control
770 SIM network reject
771 Call setup in progress
772 SIM powered down
773 SIM file not present
791 Param count not enough
792 Param count beyond
793 Param value range beyond
794 Param type not match
795 Param format invalid
796 Get a null param
797 CFUN state is 0 or 4

代码无国界,世界上有很多愿意奉献的程序员,但是少有愿意奉献的商人。能GOOGLE,尽量GOOGLE吧。

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
很高兴为您提供帮助! 下面是一个简单的示例代码,您需要将其修改为适合您的硬件和网络设置的版本。 ``` #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include <ArduinoJson.h> #include <TFT_eSPI.h> #include <lvgl.h> // 修改为您的 WiFi SSID 和密码 const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; // 修改为您在心知天气注册的 API Key 和城市 ID const char* apiKey = "your_API_KEY"; const char* cityId = "your_CITY_ID"; // 修改为您的 TFT_eSPI 硬件设置 TFT_eSPI tft = TFT_eSPI(); #define TFT_WIDTH 240 #define TFT_HEIGHT 240 void setup() { Serial.begin(115200); // 初始化 TFT_eSPI 硬件 tft.begin(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); // 连接 WiFi Serial.printf("Connecting to WiFi %s...\n", ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.print("."); } Serial.println(""); Serial.printf("WiFi connected, IP address: %s\n", WiFi.localIP().toString().c_str()); // 初始化 LVGL lv_init(); lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = TFT_WIDTH; disp_drv.ver_res = TFT_HEIGHT; disp_drv.flush_cb = [](lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p) { tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, area->x2, area->y2); uint32_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1); tft.pushColors((uint16_t*)color_p, size, true); tft.endWrite(); lv_disp_flush_ready(disp_drv); }; lv_disp_drv_register(&disp_drv); lv_theme_t* theme = lv_theme_material_init(210, NULL); lv_theme_set_current(theme); // 创建 LVGL 控件 lv_obj_t* label_time = lv_label_create(lv_scr_act(), NULL); lv_obj_align(label_time, NULL, LV_ALIGN_CENTER, 0, -50); lv_label_set_text(label_time, "Loading..."); lv_obj_t* label_date = lv_label_create(lv_scr_act(), NULL); lv_obj_align(label_date, NULL, LV_ALIGN_CENTER, 0, 50); lv_label_set_text(label_date, "Loading..."); // 更新天气和时间 updateWeather(); updateTime(); } void loop() { // 每分钟更新一次时间和天气 static uint32_t lastUpdateTime = 0; if (millis() - lastUpdateTime >= 60000) { updateWeather(); updateTime(); lastUpdateTime = millis(); } // 处理 LVGL 事件 lv_task_handler(); delay(5); } void updateWeather() { // 发送 HTTP 请求获取天气数据 WiFiClientSecure client; if (!client.connect("api.seniverse.com", 443)) { Serial.println("Failed to connect to weather server"); return; } String url = "/v3/weather/now.json?key=" + String(apiKey) + "&location=" + String(cityId); client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: api.seniverse.com\r\n" + "User-Agent: ESP32\r\n" + "Connection: close\r\n\r\n"); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { Serial.println("Failed to receive weather data"); return; } delay(100); } // 解析 JSON 数据 String line = client.readStringUntil('\n'); while (!line.startsWith("{")) { line = client.readStringUntil('\n'); } DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, line); if (error) { Serial.println("Failed to parse weather data"); return; } JsonObject weather = doc["results"][0]["now"]; const char* text = weather["text"]; int temperature = weather["temperature"]; // 更新 LVGL 控件 lv_obj_t* label_weather = lv_label_create(lv_scr_act(), NULL); lv_obj_align(label_weather, NULL, LV_ALIGN_CENTER, 0, 100); lv_label_set_text_fmt(label_weather, "%s %d°C", text, temperature); } void updateTime() { // 获取当前时间 time_t now = time(nullptr); struct tm* timeinfo = localtime(&now); // 更新 LVGL 控件 lv_obj_t* label_time = lv_scr_act()->child_ll; lv_label_set_text_fmt(label_time, "%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min); lv_obj_t* label_date = label_time->sibling_ll; lv_label_set_text_fmt(label_date, "%04d-%02d-%02d", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday); } ``` 请注意,此示例代码需要您在心知天气注册并获取 API Key 和城市 ID。此外,您需要安装 TFT_eSPI 和 LVGL 库,并将其与您的硬件设置匹配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值