【问题标题】:ESP8266 NodeMCU can't send TCP commands - same code works on Arduino Uno?ESP8266 NodeMCU 无法发送 TCP 命令 - 相同的代码适用于 Arduino Uno?
【发布时间】:2020-07-08 17:20:16
【问题描述】:

我是这方面的菜鸟,所以希望是一个愚蠢的显而易见的人。

我正在尝试创建一个简单的温度/湿度传感器,它从 DHT22 获取读数并使用 ESP8266 将它们 ping 到 Thingspeak 的 API,然后绘制/存储等。

我在下面粘贴了代码 - 它适用于 Arduino Uno,我正在尝试将其缩小到 ESP8266 上,这样我就可以在房子周围生产许多小型温度传感器。

症状

  • wifi 连接正常
  • 它正在生成正确的 API 字符串(我已经通过手动剪切并将其粘贴到浏览器中进行了测试)
  • 温度传感器也生成正确的读数
  • 它返回“数据失败!”在串行监视器中,表明这是代码中发生错误的位置

我不知道从 Arduino Uno 迁移到 ESP8266 是否有什么奇怪的地方会导致问题(即需要不同的库,TCP 命令不同等)

非常感谢经验丰富的老手的任何帮助!

这是串行监视器输出的 sn-p 和代码(只是密码/api 等)

22:16:50.266 -> **************
22:16:57.579 -> Wifi Connection Successful
22:16:57.579 -> The IP Address of the Sensor is:192.168.1.211
22:16:57.579 -> Humidity: 41.50
22:16:57.579 -> Temperature: 21.70
22:16:57.579 -> AT+CIPSTART="TCP","api.thingspeak.com",80
22:17:00.574 -> AT+CIPSEND=63
22:17:01.561 -> AT+CIPCLOSE
22:17:02.577 -> Data Fail!
22:17:02.577 -> GET /update?apikey=<REMOVED>&field1=21.70&field2=41.50


#include<stdlib.h>
#include "DHT.h"
#include <ESP8266WiFi.h>

#define SSID "<REMOVED>" //your network name
#define PASS "<REMOVED>" //your network password
#define API "<REMOVED>" //api string
#define IP "api.thingspeak.com" // thingspeak.com
#define DHTPIN 4     // what pin the DHT sensor is connected to
#define DHTTYPE DHT22   // Change to DHT22 if that's what you have
#define Baud_Rate 115200 //Another common value is 9600
#define DELAY_TIME 300000 //time in ms between posting data to ThingSpeak

//Can use a post also
String GET = String("GET /update?apikey=") + API + "&field1=";
String FIELD2 = "&field2=";

//if you want to add more fields this is how
//String FIELD3 = "&field3=";

bool updated;

DHT dht(DHTPIN, DHTTYPE);

//this runs once
void setup()
{
  delay(5000);
  Serial.begin(Baud_Rate);
  // Connect to WIFI
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print("*");
  }

  Serial.println("");
  Serial.println("Wifi Connection Successful");
  Serial.print("The IP Address of the Sensor is:");
  Serial.println(WiFi.localIP()); //Print the IP Address


  //initalize DHT sensor
  dht.begin();
}

//this runs over and over
void loop() {
  float h = dht.readHumidity();
  Serial.print("Humidity: ");
  Serial.println(h);
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float c = dht.readTemperature();
  Serial.print("Temperature: ");
  Serial.println(c);

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(c)) {
    Serial.println("Reading DHT22 Failed, exiting");
    return;
  }

  //update ThingSpeak channel with new values
  updated = updateTemp(String(c), String(h));

   //wait for delay time before attempting to post again
  delay(DELAY_TIME);
}

bool updateTemp(String tempC, String humid) {
  //initialize your AT command string
  String cmd = "AT+CIPSTART=\"TCP\",\"";

  //add IP address and port
  cmd += IP;
  cmd += "\",80";

  //connect
  Serial.println(cmd);
  delay(2000);
  if (Serial.find("Error")) {
    return false;
  }

  //build GET command, ThingSpeak takes Post or Get commands for updates, I use a Get
  cmd = GET;
  cmd += tempC;
  cmd += FIELD2;
  cmd += humid;
  cmd += "\r\n";  

  //continue to add data here if you have more fields such as a light sensor
  //cmd += FIELD3;
  //cmd += <field 3 value>

  //Serial.println(cmd);
  //Use AT commands to send data
  Serial.print("AT+CIPSEND=");
  Serial.println(cmd.length());
  if (Serial.find(">")) {
    //send through command to update values
    Serial.print(cmd);
  } else {
    Serial.println("AT+CIPCLOSE");
  }

  if (Serial.find("OK")) {
    //success! Your most recent values should be online.
    Serial.println("Data Sent!");
    return true;
  } else {
    Serial.println("Data Fail!");
    Serial.println(cmd);
    return false;
  }
}

boolean connectWiFi() {
  //set ESP8266 mode with AT commands
  Serial.println("AT+CWMODE=1");
  delay(2000);

  //build connection command
  String cmd = "AT+CWJAP=\"";
  cmd += SSID;
  cmd += "\",\"";
  cmd += PASS;
  cmd += "\"";

  //connect to WiFi network and wait 5 seconds
  Serial.println(cmd);
  delay(5000);

  //if connected return true, else false
  if (Serial.find("OK")) {
    Serial.println("WIFI connected");
    return true;
  } else {
    Serial.println("WIFI not connected");
    return false;
  }
}


【问题讨论】:

  • 一个问题和两个评论:我找不到仅通过 WiFi 串行发送数据的部分。在 ESP(以及 Arduinos 顺便说一句)上永远不要使用延迟。这会停止处理(因此所有 wifi/spiffs a.s.o)函数,并且出于稳定性原因,从不使用 String 类。它会破坏你的堆并使 ESP 崩溃 - 使用编译到闪存的固定字符数组并使用 const 字符的指针(即命令序列、文本 aso。)作为测试:从 PC 上的 USB 中删除你的 ESP 并尝试获取数据 OTA(无线)
  • Re wifi - 是的,它应该通过 wifi 上的串行 TCP 连接发送,即这部分://Serial.println(cmd); //使用AT指令发送数据 Serial.print("AT+CIPSEND="); Serial.println(cmd.length()); if (Serial.find(">")) { //通过命令发送更新值 Serial.print(cmd); } else { Serial.println("AT+CIPCLOSE"); }
  • 重新延迟 - 有趣,以前从未听说过 - 实际上这都是来自许多教程,他们说您必须使用延迟来确保它不会破坏服务器等?那么我应该如何建立延迟?
  • 重新测试 - 将它与 PC 分离,看看会发生什么?

标签: tcp arduino esp8266 esp8266wifi


【解决方案1】:

首先在我们讨论您面临的问题之前,您需要了解使用 ESP8266 的两种不同方式:

  • 将其用作 WiFi 调制解调器;
  • 将其用作独立的 MCU。

当将它用作 WiFi 调制解调器时,您使用 AT-command 与它通信,默认情况下,大多数 ESP8266 模块都是这样做的,它带有 At-command 固件。

当您使用 ESP8266 作为 MCU 时,您使用 Arduino 草图刷新 ESP8266,它会覆盖 AT-Command 固件并使用 Arduino 引导加载程序上传,并允许 ESP8266 像 Arduino 一样用作独立 MCU .

您提到在使用 Arduino Uno 时可以毫无问题地与 ESP8266 通信,这表明您正在使用 AT 命令与 ESP8266 通信(并且您的部分代码表明是这种情况)。

当您将 Arduino 草图上传到 ESP8266 以将其用作独立 MCU 时,您需要使用 Arduino 库(例如 ESP8266WiFi)与 ESP8266 通信,该库将负责与 ESP8266 芯片的较低级别通信。这就是为什么你可以使用ESP8266WiFi类提供的能力来建立WiFi。

但是,updateTemp() 中的代码仍在使用不再起作用的 AT 命令。您将需要使用ESP8266WebClientESP8266HTTPClient(这个更简单、更容易)库来建立http 连接。这是我为使用ESP8266HTTClient 库处理HTTP 请求而修改的代码。顺便说一句,编译好的代码,但我没有用 thinkspeak 服务器测试它,因为我不使用 thinkspeak,也没有 api-key。

#include <DHT.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

#define SSID "<REMOVED>" //your network name
#define PASS "<REMOVED>" //your network password
#define API "<REMOVED>" //api string
#define IP "api.thingspeak.com" // thingspeak.com
#define PORT 80
#define DHTPIN 4     // what pin the DHT sensor is connected to
#define DHTTYPE DHT22   // Change to DHT22 if that's what you have
#define BAUD_RATE 115200 //Another common value is 9600
#define DELAY_TIME 300000 //time in ms between posting data to ThingSpeak

DHT dht(DHTPIN, DHTTYPE);

//this runs once
void setup()
{
  Serial.begin(BAUD_RATE);

  // Connect to WIFI
  WiFi.begin(SSID, PASS);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print("*");
  }
  Serial.println("");
  Serial.println("Wifi Connection Successful");
  Serial.print("The IP Address of the Sensor is:");
  Serial.println(WiFi.localIP());

  //initalize DHT sensor
  dht.begin();
}

//this runs over and over
void loop() {
  float h = dht.readHumidity();
  Serial.print("Humidity: ");
  Serial.println(h);
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float c = dht.readTemperature();
  Serial.print("Temperature: ");
  Serial.println(c);

  // Check if any reads failed and exit early (to try again).
  if (isnan(h) || isnan(c)) {
    Serial.println("Reading DHT22 Failed, exiting");
    return;
  }

  //update ThingSpeak channel with new values
  updateTemp(c, h);

  //wait for delay time before attempting to post again
  delay(DELAY_TIME);
}


bool updateTemp(float tempC, float humid) {

  WiFiClient client;    // Create a WiFiClient to for TCP connection

  if (!client.connect(IP, PORT)) {
   Serial.println("HTTP connection failed");
   return false;
  }

  Serial.println("Sending data to server");
  if (client.connected()) {
    client.print("GET /update?api_key="); client.print(API);
    client.print("&field1="); client.print(String(tempC));
    client.print("&field2="); client.print(String(humid));
    client.println(" HTTP/1.1");
    client.print("Host: "); client.println(IP);
    client.println("Connection: close");
    client.println();    //extra "\r\n" as per HTTP protocol
  }

  // wait for data to be available
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
     Serial.println("HTTP Client Timeout !");
     client.stop();
     return false;
    }
  }

  Serial.println("Receiving HTTP response");
  while (client.available()) {
   char ch = static_cast<char>(client.read());
   Serial.print(ch);
  }
  Serial.println();

  Serial.println("Closing TCP connection");
  client.stop();
  return true;
}

【讨论】:

  • hcheung - 如果有一个完美的 stackoverflow 答案的广告,你就已经中奖了。非常感谢您花时间如此完整地回答 - 从您的介绍到解释整个概念,到您的逐步完成,再到您的代码示例 - 绝对精彩的回应!这对我来说完全理解了整个概念 - 完全理解你提到的一切!
  • 一件小事,以防万一有人尝试使用上面的代码 - 发现其中一个 api 字符串有错字(这一行中的“字段” - client.print("&filed1="); client.print(String(tempC));) - 完全没有问题要修复!
【解决方案2】:

您必须解决三个问题:
摆脱 delay() 并不是说​​您不应该不时调用例程来不锤击您的服务器。原因:delay() 函数在指定的时间内停止 CPU。
在设置中可以等待一些硬件初始化,并可以快速测试/调试简单的例程。服务器/客户端场景中的坏主意(通信任务与用户任务并行运行)并且在例如带有 2 个 CPU 的 ESP32。一个简短的如何为您的程序。

unsigned long previousMillis = 0; 

setup() {
 ....
} 

loop(){
  if (millis() - previousMillis > DELAY_TIME) {   // we check for elapsed time

   // What ever needs to be done all DELAY_TIME ms  

   previousMillis  = millis(); // we "reset" the timer
   }
// other code which needs no delayed execution
}

同时更改 Arduino 的代码并将其提升到一个新的水平-
第二点:去掉String类,改用固定字符。
原因是String类的内存管理不好,破坏了你的堆,导致ESP迟早崩溃,chars编译为flash,没有这个问题.如何使用您的命令/功能之一的简短示例:

// instead of #define API "<REMOVED>" //api String use
const char* API = "<REMOVED>" // pointer to the char API

//Can use a post also
char fixPartGET[128] = '\0';  // takes 255 chars and the terminator, if needed enlarge
char cmd[256] = '\0';  // takes 127 chars and the terminator, if needed enlarge
char numBuffer [16] = '\0'; // we use for float conversions
strcpy (fixPartGET, "GET /update?apikey="); // Copy empties
strcat (fixPartGET, API);
strcat (fixPartGET, "&field1=");
const char* FIELD2 = "&field2=";

setup(){
    ....
}

loop(){
    ....
}

bool updateTemp( float c, float h) {

  if (WiFi.status() != WL_CONNECTED) {
    return false;
  }

  //build GET command, ThingSpeak takes Post or Get commands for updates, I use a Get
  strcpy(cmd, fixPartGET); // start building the GET command
  // The dtostrf() function converts the double value passed in into an ASCII representation
  dtostrf(c, 6, 2, numBuffer); // change to what you need as precision
  strcat(cmd, numBuffer);
  strcat(cmd, FIELD2);
  dtostrf(h, 6, 2, numBuffer); // change to what you need as precision
  strcat(cmd, numBuffer);
  strcat(cmd, "\r\n");  

  //continue to add data here if you have more fields such as a light sensor
  //strcat(cmd, FIELD3);

  //Some other code to discuss
return true;
}

最后一点
与 AP 的 WiFi 通信 - 查看 WiFi 库的示例并使用串行 AT 命令解决麻烦。目前,这些命令通过 USB 电缆而不是 WiFi 运行。与 Arduino 相比,nodeMCU 是一台功能强大的机器,但由于技术概念,它更容易出现不稳定。 我提出的更改 1 和 2 可以而且应该在 Arduino 上实现,作为第一次习惯这种方法。如果运行此程序,请在 NodeMCU 上将 AT 更改为 WiFi 客户端功能。 ESP8266WiFi.h 库示例 WiFiClientBasic 和 WiFiClient 可能有助于快速获得结果(查找 client.print(...) )。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多