本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

基于Arduino的WIFI项目
发布时间:2021-08-31
分享到:
基于Arduino的WIFI项目
发布时间:2021-08-31
分享到:

与外部网络通信是 Arduino 项目的一项重要功能,并且对许多项目来说是强制性的。

在本教程中,我们将创建一个 Arduino WiFi 项目,该项目允许您向它发送命令以打开/关闭通过 WiFi 从计算机连接到 Arduino 的 LED。

开发周期如下:

  1. 我们将使用 Arduino Mega+ESP8266 WiFi 板进行开发和调试
  2. 一旦它正常工作,我们就会将该项目移至 Arduino Uno + ESP8266 WiFi Shield

这个教程也很容易适应其他硬件,比如 ESP8266 WiFi 使用 Arduino 上的主要“串行”设备与其交互 - 例如许多蓝牙模块。

Arduino 通信有很多选择,我经常使用有线以太网或 USB,因为我有可用的基础设施,它通常易于使用并且对我有用。但有时无线方法会容易得多。

有许多无线连接选项可用,例如 LoRa、蓝牙、IR、WiFi 等。它们每个都有不同的属性和理想的用例。可以说最普遍的是 WiFi,这就是本文要讨论的内容。

然而,我拥有的所有适用于 Arduino 的 WiFi 解决方案(ESP-8266 解决方案)和我在网上阅读的其他解决方案,恕我直言,使用起来非常乏味(即痛苦)。Arduino 项目开发周期需要硬件黑客和/或 DIP 开关的不断切换才能使用它们。

我提到的乏味是因为 Arduino 和 WiFi 硬件之间的交互是通过 Arduino 的“串行”设备进行的。默认情况下,Arduino 串行设备用于两件事:
Arduino 和 WiFi 对 Serial 的共享使用在所有意图和目的上都是一个冲突 - 也就是说,Serial 是用于将新程序(/sketch)上传到 Arduino,还是用于向 WiFi 发送命令或它是否用于输出调试消息?在许多 WiFi 板上实现的这种冲突的解决方案是使用一系列 DIP 开关或需要额外的硬件来解决串行端口的用途的困境。

这意味着当您正在开发您的 Arduino 程序并希望在更改后上传它时,您需要将 DIP 开关设置为“特定配置”(串行设备连接到 USB)以允许上传更新的程序.

当您完成上传并想要运行您的新代码时,您需要将 DIP 开关设置为不同的“特定配置”(串行设备连接到 WiFi)并重置您的 Arduino 以重新启动程序 - 或者有一些导致程序在开始发出 WiFi 指令之前等待的其他机制(例如,延迟让您有时间重置开关或按下按钮等)。

当然,如果您的程序中有调试消息通常会输出到串行设备并因此在 Arduino IDE 的串行监视器中可见,那么您就不走运了,因为这些调试消息将被发送到您猜对了的 WiFi不太可能处理它们的设备。

测试后,您确定新的代码更改、添加新功能或任何需要重置开关以进行上传的内容,然后重新重置开关以运行修改后的 ode。每次更改代码时都需要重复此循环 - 无论更改有多小。

对我来说,这非常乏味,并且在某些板上,如果您不小心,开关的切换有损坏它的风险 - 或者换句话说,恕我直言,这是非常痛苦的!

一种选择是使用 SoftwareSerial 或者如果你有它,另一个串行端口(例如 Leonardo 上的 Serial1)用于调试消息或 WiFi 交互,但这需要某种转换器(例如另一个 Arduino、FTDI USB 串行转换器等)将 SoftwareSerial/Serial1“端口”连接到 PC 等等。并且没有解决主要串行设备用于程序上传以及与 WiFi 模块通信的主要问题。

我在使用 SoftwareSerial 时遇到的另一个潜在问题,我发现特别令人沮丧的是,对于与 WiFi 板的每次通信,我经常需要输出几个调试消息来隔离问题。这可能(并且已经)导致数据丢失、串行监视器上的消息损坏和其他问题。这意味着您可能正在尝试调试一个真正不存在的问题,因为它只是由通过 SoftwareSerial 传递的负载引起的。在我的测试中,当通过硬件串行端口发送相同的调试消息时,这个“数据溢出”问题似乎没有发生。

所以这篇文章是关于如何避免痛苦并拥有正常的代码开发周期,上传,测试,根据需要重复 - 没有DIP开关痛苦来解决串行冲突问题。

零件清单:
本项目主要使用Arduino Mega + WiFi进行开发

项目完成后,我们会将代码移植到 Arduino Uno + WiFi(或任何其他带有 ESP WiFi 扩展板的 Arduino)

Arduino Mega + WiFi 板(可从 Jaycar XC-4421 获得)

  • 一个中型面包板
  • 3 个 LED
  • 3 个限流电阻 (440+ ohm)
  • 连接线

可选

  • Arduino
  • Duinotech ESP13 WiFi shield 
  • Arduino Uno + Wifi 板

第 1 步:连接组件

在这个项目中,我们将在 Mega 上构建软件,然后将代码(和测试电路)迁移到 Uno。

您将需要以下组件:

  • Arduino Mega+WiFi
  • 面包板
  • 3 个 LED 和限流电阻
  • 连接线

电路相当简单(参见图表),所以我不会详细解释它。确保将带有扁平侧/短腿的 LED 连接到电阻器,电阻器又接地。

测试程序使用数字 I/O 引脚 5、6 和 7,因此将 LED(通过长腿)连接到 Arduino Mega 上的这些引脚。我使用了不同颜色的 LED,但如果您没有多种颜色,则不必这样做。此外,如果您有不同的颜色,顺序并不重要,只需将一个 LED(+ 限流电阻)连接到 DIO 引脚 5、6 和 7 中的每一个。

照片、面包板和电路图说明了如何进行连接。

第 2 步:在 Mega 上设置 DIP 开关

在 Arduino Mega+WiFi 上,您应该找到我在照片中突出显示的 2 组开关。在我的板上,一个开关是一个 8 路 DIP 开关块(带有需要工具(例如小螺丝刀)才能移动的微小开关)。另一个开关是一个更大的开关,它是一个滑块的形式。

为了尽量减少混淆,我将所有 Arduino 板上可用的主串行端口称为 Serial(0)。在代码中,这是名为 Serial 的串行设备,通常连接到 Arduino 板上的 USB 端口。

第一块 8 个开关控制板上的主要通信路径。选项包括:

  • 将 Mega 的串行端口连接到 ESP(打开 1 和 2)
  • 将 USB 连接到 Mega 的串行(0)端口(打开 3 和 4)
  • 将 USB 连接到 ESP(打开 5 和 6)
  • 使 ESP 在下次复位时进入编程模式(开关 7 打开)
  • 开关 8 未使用

第二个大开关决定了 Mega 如何(即通过哪条路径)连接到 ESP(开关 1 和 2)。此开关控制使用 Mega 的哪个串行端口与 ESP 通信。选项为 Serial(0) 或 Serial3。

这意味着我们可以:

使用 Mega 的 Serial3 端口与 ESP 通信,从而利用其 WiFi 功能。

以传统方式使用 USB 将代码上传到 Mega 并通过串行监视器与 Mega 的程序交互,而无需在编程和测试时不断调整开关 1&2 和开关 3&4。
简而言之,此开关解决了与我在步骤 1 中概述的 Serial(0) 端口相关的硬件冲突。

要进行设置,请按如下方式配置开关:

  • 转动开关 1、2、3 和 4 -> ON
  • 转动开关 5, 6, 7 & 8 -> OFF
  • 大滑块应设置在 TXD3 和 RXD3 端(即远离 USB 连接器和 8 路 DIP 开关的位置)

电路图显示了我如何相信两个开关很可能连接。我这样说是因为我在网上发现了很多图表(我怎么能这么说?)似乎并没有准确反映板上的实际电路 - 有些人可能会说我发现的图表“完全错误”。

我通过广泛的测试/反复试验制作了这个电路图。在我的图中,每个开关对上的 TX 和 RX 线可能颠倒了,但这应该无关紧要,因为开关应始终成对使用(即开关 1 和 2 应该设置相同的方式,类似地 3 和 4 应该设置方式与开关 5 和 6 相同)。

第 3 步:开发您的 Arduino WiFi 应用程序

由于我将提供 Arduino 代码,因此不会有太多的开发周期,但我将尝试通过建议更改来说明开发周期,以说明我们可以在下一步中轻松重新上传和运行修改后的代码.

关键的一点是,我们可以做到这一点,而无需经常更换开关,也无需额外的支持硬件。我们将看到如何配置 Arduino Mega + WiFi,以便您可以使用 Arduino <-> PC USB 连接来:

  • 上传编译代码
  • 通过串行监视器
  • 与运行在 Arduino 上的代码进行交互
  • 使用无线网络

话虽如此,我们需要对 Arduino Mega+WiFi 上的开关进行初始设置以支持此开发过程(如上一步所述)。如果您尚未设置开关,请返回上一步并立即设置

我们将用于开发的代码可以从我的GitHub下载或从下面复制粘贴

注意有两个文件。第一个文件可以任意命名(我叫我的 ArduinoESPInteractive.ino)。第二个文件必须命名为 NullSerial.h

只需在 Arduino IDE 中创建一个新项目,并使用下面显示的两个文件中的代码或来自GitHub 的代码

如果您手动输入代码或复制/粘贴,那么您还需要手动添加第二个文件。要添加第二个文件,只需单击 Arduino IDE 编辑器右上角的小向下箭头,然后选择“新选项卡”,然后将其命名为NullSerial.h

确保您已按照上一步所述设置 Mega+WiFi 上的 DIP 开关
照常上传代码。打开串行监视器,将波特率设置为 115200,观察一些我将在下一步中描述的启动消息

这是主程序(ArduinoESPInteractive.ino):

* MegaESPInteractive
 * ------------------
 * 
 * This program is designed for use with an Arduino Mega + ESP 8266 combo board.
 * It allows you to: 
 * - interact with the ESP 8266 via the Arudino
 *   and
 * - upload programs to the Arduino
 * without constantly switching the DIP switches to connect the Arduino to the ESP
 * and/or the USB.
 * 
 * If you want to upload a program to the ESP, you will still need to set the DIP
 * switches.
 * 
 * The ability to have a development cycle that does not involve switching DIP
 * switches expedites the program/debug cycle of WiFi based apps because the very
 * tedious and fiddly step of constantly having to change them is eliminated. This
 * will also probably extend the life of the board as you don't have as much
 * mechanical wear and tear on them.
 * 
 * Hardware required:
 * - Arduino Mega + ESP 8266 wifi combo board such as the Jaycar XC4421: 
 *      https://www.jaycar.com.au/mega-with-wi-fi/p/XC4421.
 * - optionally an FTDI USB - Serial board such as the Jaycar XC4672:
 *      https://www.jaycar.com.au/isp-programmer-for-arduino-and-avr/p/XC4627)
 *
 * The development cycle works as follows:
 * 1) Develop your code and upload using the standard Arduino IDE upload mechanism.
 * 2) use Serial3 (via a preprocessor #define constant) for interaction with the
 *    ESP.
 * 2) Optionally interact with your Arduino program via Serial2 or Serial (also via
 *    a preprocessor constant)
 * 3) Output Debug messages to Serial (via a preprocessor constant).
 * 
 * Preprocessor symbols are used to represent the two Serial ports used as follows:
 *   ESP -> Serial3
 *   HOST-> Serial or Serial2
 *
 * Once your program is complete and can operate standalone, you can transfer to a
 * smaller device (e.g. the Arduino UNO + Wifi board). Simply redefine the constants
 * so that ESP is Serial and HOST is an instance of softwareSerial.
 * 
 * To use this program, two sets of switches on the Arduino Mega+ESP8266 Wifi must
 * be set as follows:
 * 
 * The large DPDT switch with labels RXD0 & TXD0 at one end and RXD3 & TXD3 must be
 * set to the RXD3 & TXD3 end. This enables the Mega Serial3 port for communications
 * with the ESP.
 *
 * The 8 position DIP switch must be set as follows:
 * 
 *    on      o  o  o  o
 *    off                 o  o  o  o
 * Number     1  2  3  4  5  6  7  8
 *
 * That is, switches 1, 2, 3 & 4 are be turned on and
 * switches 5, 6, 7 & 8 are turned off.
 * These switch configurations:
 * - Connect the Mega Serial3 to the ESP (for WiFi access)
 * - Connect the Mega Serial to the USB (for programming and debugging).
 
 */
#include "NullSerial.h"

#define VERSION "1.1.0.1"
/*
 * Revisions.
 * 1.1.0.1
 *    Corrected bug for LED on/off debug messages.
 *
 * 1.1.0.0
 *    Added concept of debug messages.
 *    Added initialisation of WiFi to station mode and connect to WiFi boiler
 *    plate code.
 * 
 */

#if defined(ARDUINO_AVR_MEGA2560)
  #define HOST_BAUD 115200
  #define HOST_RX  "USB(0)"
  #define HOST_TX  "USB(1)"
  #define HOST  Serial
  #define DEBUG Serial
  
//  NullSerial _debug(1, 2);
//  #define DEBUG _debug

  #define ESP_BAUD  115200
  #define ESP_RX  15
  #define ESP_TX  14
  #define ESP   Serial3

#else if defined(ARDUINO_AVR_UNO)
/*
 * On Uno, we only have one hardware serial device.
 * So, we will use SoftwareSerial for any interactions that may be required with
 * the host.
 */
  #include <SoftwareSerial.h>
  #define HOST_BAUD 115200
  #define HOST_RX  10
  #define HOST_TX  11
  SoftwareSerial _host(HOST_TX, HOST_RX);     /* (my_RX, my_TX) */
  #define HOST  _host
  
  NullSerial _debug(HOST_TX, HOST_RX);
  #define DEBUG _debug
//  #define DEBUG _host

  #define ESP_BAUD  115200
  #define ESP_RX  "USB(0)"
  #define ESP_TX  "USB(1)"
  #define ESP   Serial

#endif

/* A pair of macros that allows the value of symbols defined in #define
 * preprocessor directives (e.g. HOST) to be output as strings (e.g. in
 * print function calls).
 */
#define _STRING(x) (#x)
#define STRING(x) _STRING(x)

/*****************************
 * The hasEOLJustBeenSent is used to track whether an end of line has been sent to
 * the ESP.
 * The version of code shipped with the device requires a Carriage Return (CR)
 * followed by a Line Feed (LF) - in that order - to mark the end of a line (EOL).
 * That is, the ESP requires a CRLF end of line (EOL) sequence.
 * 
 * This program allows you considerable flexibility of windows terminal programs
 * (I use Putty, CoolTerm, the Arduino Serial monitor and others) in their default
 * configurations. These programs all generate various combinations of the common
 * line endings CR only, LF only and CRLF.
 * When it sees a CR ('\r') or LF ('\n'), the program will send an EOL to the ESP.
 * To allow for terminal programs that send both a CR and a LF, the
 * hasEOLJustBeenSent is used to track that an EOL has been sent when either the CR
 *  or LF is received from the HOST.
 * To prevent doubling up on EOL's, if we have just received a CR or LF, and the
 * next character is also a CR or LF, then it will simply be ignored.
 */
boolean hasEOLJustBeenSent = false;
char buf[100];
int bufPtr = 0;

boolean OKseen = false;
boolean ERRORseen = false;
boolean FAILseen = false;
boolean TIMEOUTseen = false;


/*
 * processInput(msg)
 * -----------------
 * 
 * Processes a message received from the ESP.
 * This includes processing responses to commands (e.g. OK, FAIL etc)
 * Processing input from a client.
 * Processing client connection/disconection messages.
 * And others as required.
 * 
 */
void processInput (const char * msg) {
  DEBUG.println();
//  DEBUG.print(F("Received: "));
//  DEBUG.println(msg);

  OKseen = false;
  ERRORseen = false;
  FAILseen = false;

  if (strncmp(msg, "+IPD", 4) == 0) {   // Client message?
    DEBUG.println(F("Received input from client"));
    char led = msg[9];                  // Extract the LED number
    char setting = msg[10];             // Extract the setting (on/off)
    if (led >= '1' && led <= '3') {     // Check the LED number for validity (i.e. 1, 2 or 3)
      led = led - '1';                  // if valid, convert to an integer.
    } else {                            // Otherwise print an error message to the USB.
      HOST.print(F("Invalid LED: ")); HOST.println(led);
      return;
    }

    DEBUG.print(F("Setting LED ")); DEBUG.print(led);
      DEBUG.print(F(" on DIO ")); DEBUG.print(led + 5);
      DEBUG.println(setting == '+' ? " on" : " off");
                                              // FInally, turn the requested LED on or off
    digitalWrite(led + 5, setting == '+');    // depending upon the value in "setting".
                                              // The led + 5 adjusts the LED value (0, 1 or 2)
                                              // to the corresponding Arduino digital I/O pins
                                              // which that have LED's connected whihc are pins 5, 6 & 7.
  }
  OKseen = strcmp(msg, "OK") == 0;
  ERRORseen = strcmp(msg, "ERROR") == 0;
  FAILseen = strcmp(msg, "FAIL") == 0;
}

/*  accumulateESPData
 *  =================
 *  
 *  Check if a character is available on the ESP Serial port.
 *  If it is, accumultate the character in a buffer.
 *  When a newline is observed, process the input.
 *
 *  Returns true when a newLine has been seen and the input processed.
 *          false otherwise
 */
boolean accumulateESPData() {
  boolean newLineSeen = false;

  if (ESP.available()) {
    char ch = ESP.read();
    HOST.write(ch);
    if (ch == '\r' || ch == '\n') {
      buf[bufPtr] = '\0';           // Null terminate the input;
      if (bufPtr > 0) {
        processInput(buf);
        newLineSeen = true;
      }
      bufPtr = 0;
    } else {
      if (bufPtr < sizeof(buf) - 1) {   // Append the character to
        buf[bufPtr++] = ch;         // the input buffer if space permits.
      }
    }
  }
  return newLineSeen;
}


/**
 * sendESP(msg)
 * ============
 * Send the supplied msg to the ESP along with a CRLF line ending.
 * Wait for a response of OK, ERROR, FAIL or a TIMEOUT.
 * The timeout period is specified by the toPeriod constant value.
 */
const unsigned long ESP_TO_PERIOD = 30000;    // millis to wait for reply (30 secs).
void sendESP(const char *msg) {
  ESP.print(msg);
  ESP.print("\r\n");
  OKseen = false;
  ERRORseen = false;
  FAILseen = false;
  TIMEOUTseen = false;
      // Establish the initial timout period.
  unsigned long timeout = millis() + ESP_TO_PERIOD;
      // Loop until we get one of the expected replies
      // or a timeout occurs (without getting one of the expected replies)
  while (!OKseen && !ERRORseen && !FAILseen && ! TIMEOUTseen) {
    if (accumulateESPData()) {
            // A response has been recieved.
            // Debug output the status of the expected replies
      DEBUG.print(F("O,E,F="));
        DEBUG.print(OKseen); DEBUG.print(ERRORseen);
        DEBUG.println(FAILseen);
      timeout = millis() + ESP_TO_PERIOD;    // message rcvd, reset the timer.
    }
    if (millis() >= timeout) {
      TIMEOUTseen = true;
      DEBUG.println(F("*** Timeout waiting for ESP reply"));
    }
  }
}


/**
 * AccumulateHOSTData
 * ==================
 * 
 * Check for data being available from the host. If there is data, simply pass it on
 * to the ESP.
 * If a CR or a LF is observed in the HOST data, send the ESP a CRLF.
 */
void accumulateHOSTData() {
  if (HOST.available()) {
    char ch = HOST.read();
//    HOST.write(ch);                   // Perform local echo to HOST (or not)
    if (ch == '\n' || ch == '\r') {   // Do we have a LF or CR?
      if (!hasEOLJustBeenSent) {      // Yep, did we just send one?
        ESP.write('\r');              // Nope, so send a CRLF to the ESP.
        ESP.write('\n');
        hasEOLJustBeenSent = true;    // Track that we've sent an EOL
      }
    } else {                          // Not a CR & not a LF, so
      ESP.write(ch);                  // Write the character to the ESP
      hasEOLJustBeenSent = false;     // Since not a CR & not a LF, track that
                                      // we didn't just send an EOL to ESP.
    }
  }
}


/**
 * setup
 * -----
 *
 * Initialise our Serial devices, output some configuration information
 * and set the BUILTIN_LED for output.
 */
void setup() {
  ESP.begin(ESP_BAUD);
  HOST.begin(HOST_BAUD);
  while (!HOST) {
    delay(1);
  }

  HOST.print(F("Version: ")); HOST.println(F(VERSION));
  HOST.print(F("ESP  Ready on: ")); HOST.print(STRING(ESP));
    HOST.print(F(" @ ")); HOST.print(ESP_BAUD);
    HOST.print(F(" bps. RX, TX: "));
    HOST.print(ESP_RX); HOST.print(F(", ")); HOST.println(ESP_TX);
    
  HOST.print(F("HOST Ready on: ")); HOST.print(STRING(HOST));
    HOST.print(F(" @ ")); HOST.print(HOST_BAUD);
    HOST.print(F(" bps. RX, TX: "));
    HOST.print(HOST_RX); HOST.print(F(", ")); HOST.println(HOST_TX);

  DEBUG.println(F("**** Debug messages enabled ****"));
  HOST.println();

  pinMode(5, OUTPUT);             // Set some pins to output for LEDs.
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);

  DEBUG.println(F("Sending GetVersion"));
  sendESP("AT+GMR");              // Get Version Info.
    // Once off set mode and join the WiFi.
  DEBUG.println(F("Setting the mode"));
  sendESP("AT+CWMODE=1");         // Set operating mode to "station"
  DEBUG.println(F("Connect to my WiFi"));
  sendESP("AT+CWJAP=\"YourWiFi\",\"yourpassword\"");
  
  DEBUG.println(F("Sending Max Connections=1"));
  sendESP("AT+CIPMUX=1");         // Enable Server
  DEBUG.println(F("Sending Start Server on Port 80"));
  sendESP("AT+CIPSERVER=1,80");   // Open port 80

  HOST.println(F("Ready"));
  HOST.println();
}

/* Loop
 * ====
 * 
 * Accumulate characters from the HOST and ESP.
 * Process them as necessary.
 */
void loop() {
  accumulateHOSTData();
  accumulateESPData();
}

这是第二个文件,它必须命名为 NullSerial.h并且与主程序(从上面)位于同一文件夹中。当我们将项目移至 Arduino Uno + WiFi 时,我将解释此文件的用途。

 NullSerial
 * ----------
 * 
 * An empty implementation of a Serial Interface.
 * 
 * The idea for this NullSerial class is to, via conditional compilation,
 * eliminate any Debug messages from released code.
 * 
 */

 #ifndef NullSerial_h
 #define NullSerial_h

class NullSerial {
  public:
    NullSerial(uint16_t receivePin, uint16_t transmitPin, bool inverse_logic = false) {}
    NullSerial() {}
    void begin (long speed) {}
    bool listen() { return false; }
    void end() {}
    bool isListening() { return false; }
    bool stopListening() { return false; }
    bool overflow() { return false; }
    int peek() { return -1; }

    void println() {}
    void print() {}
    void println(const void * msg) {}
    void print(const void * msg) {}
    void println(const int i) {}
    void print(const int i) {}
};

#endif   //NullSerial_h

第 4 步:连接到 WiFi

如果上一步中的一切顺利,您应该在计算机上(在串行监视器中)看到类似以下内容。

Version: 1.0.0.0
ESP  Ready on: Serial3 @ 115200 bps. RX, TX: 15, 14
HOST Ready on: Serial @ 115200 bps. RX, TX: USB(0), USB(1)
**** Debug messages enabled ****

Sending GetVersion
AT+GMR
OK, ERROR, FAIL = 000
AT version:0.21.0.0
OK, ERROR, FAIL = 000
SDK version:0.9.5
OK, ERROR, FAIL = 000
OK
OK, ERROR, FAIL = 100
Ready

上面显示的消息是调试消息,显示了 Arduino 的配置和 ESP 上运行的软件版本。

如果您愿意,您可以通过将其输入到串行监视器的输入文本框中并单击“发送”按钮来向 Arduino 发送一些文本。您发送的任何文本都将直接发送到 ESP。因此,最好发送 AT 命令,例如“AT+GMR”(不带引号),它要求 ESP 报告它正在运行的软件版本。我将在本 Instructable 的其余部分展示一些有用的命令。

如果您对上述输出的含义不是特别感兴趣,只是想连接,请跳到下一个名为“连接到您的 WiFi”的部分。

初始调试消息的分析
上述输出的前几行显示了 Arduino 用于与您和 ESP 交互的配置。这些是以“ESP”和“HOST”开头的行。内容是:

正在使用的串行端口“OK, ERROR, FAIL ...”消息是 Arduino 代码在从 ESP 收到消息时生成的调试消息。这些消息显示 Arduino 程序中维护的 3 个标志的状态,这些标志的设置或清除取决于是否从 ESP 接收到消息“OK”、“ERROR”或“FAIL”。通常,ESP 会以 OK、ERROR 或 FAIL 消息结束其输出,因此这些消息用作标记来确定 ESP 何时完成对命令的响应并准备好下一个命令。我们可以在 OK 位设置为 1 的“Ready”消息之前看到这一点。

其他消息“AT 版本:0.21.0.0”和“SDK 版本:0.9.5”是 ESP 生成的实际输出。Arduino 程序将从 ESP 接收到的所有内容中继到 IDE 的串行监视器

ESP8266 WiFi 由 AT 命令驱动。如果您对此历史感兴趣,可以在Wikipedia 上找到一篇很好的文章。ESP8266 WiFi 解决方案使用与维基百科文章中提到的命令集不同的命令集,主要是因为它不是拨号调制解调器——这正是维基百科文章所讨论的内容。但是,AT 命令的结构与文章中描述的类似。参考维基百科文章,ESP8266 WiFi 似乎只在命令模式下运行。

  • 将 ESP 置于“站模式”-“AT+CWMODE=1”命令
  • 加入您的 WiFi 网络 - “AT+CWJAP=...”命令
  • 进入网络后,我们将要使用以下命令建立服务:
  • 开启“服务器模式”——“AT+CIPMUX=1”
  • 开始监听端口 - "AT+CIPSERVER=1,80"

请注意,每次重置 ESP 时都必须执行最后两个命令(打开服务器模式并开始侦听端口)。最简单的方法是在 Arduino 的 setup() 函数中运行它们。ESP 会在重置时“记住”前两个命令(站模式和加入 WiFi)中的设置。所以一旦成功运行,如果你想这样做,你可以将它们注释掉。每次 Arduino 重置时都运行它们没有坏处,当我们移动到 Uno 和/或如果出于某种原因更换了 WiFi 硬件(例如,有故障的 WiFi 屏蔽被替换为不错的)。因此,我倾向于不将它们注释掉并在每次 Arduino 启动时运行它们,以确保我们拥有预期的操作环境。

在主 Arduino 程序的 setup 函数中,大约在第 330 行,您会注意到以下代码:

  DEBUG.println(F("Sending GetVersion"));
  sendESP("AT+GMR");              // Get Version Info.
    // Once off set mode and join the WiFi.
  DEBUG.println(F("Setting the mode"));
  sendESP("AT+CWMODE=1");         // Set operating mode to "station"
  DEBUG.println(F("Connect to my WiFi"));
  sendESP("AT+CWJAP=\"YourWiFi\",\"YourPassword\"");

  DEBUG.println(F("Sending Max Connections=1"));
  sendESP("AT+CIPMUX=1");         // Enable Server
  DEBUG.println(F("Sending Start Server on Port 80"));
  sendESP("AT+CIPSERVER=1,80");   // Open port 80

您需要通过替换 YourWifi 和 YourPassword 来修改“AT+CWJAP”行(如下所示)以包含您的 WiFi 和密码:

  sendESP("AT+CWJAP=\"YourWiFi\",\"YourPassword\"");

注意不要去掉围绕 YourWiFi 和 YourPassword 的反斜杠或双引号。这些是确保编译器将双引号插入到发送到 sendESP 函数的字符串中所必需的。

完成此操作后,重新上传程序。记住这里的关键要点是您只需在以正常方式编辑后上传代码,您不必调整任何开关,也不需要任何额外的硬件来调试您的程序。

上传完成后,您应该会在串行监视器上看到更多消息。

我们正在寻找的主要两条消息是对加入 WiFi 网络 (AT+CWJAP=...) 并开始侦听端口 (AT+CIPSERVER=...) 的命令的“OK”响应。

如果一切顺利,您可以尝试在串口监视器中输入以下“查询网络配置命令”(AT+CIFSR)。您应该会看到如下内容:

AT+CIFSR<br>+CIFSR:STAIP,"192.168.3.160"<br>+CIFSR:STAMAC,"18:fe:34:2c:70:45"<br>OK

重要的部分是包含文本“+CIFSR:STAIP”的行,它是我们的 IP 地址。我们将在下一步打开和关闭一些 LED 时需要 IP 地址!

就我而言,地址是 192.168.3.160 - 您的 IP 地址几乎肯定会有所不同。记下您的 IP 地址,而不是我的。

第 5 步:通过 WiFi 与应用的互动

现在我们已经连接到 WiFi 并开始我们的服务,我们将尝试联系它并让这些 LED 跳舞 - 可能不是很跳舞,但肯定会打开和关闭

在我们开始之前,让我们检查“开始监听端口”命令(AT+CIPSERVER=1,80)的结构

这个命令的意思可以分解如下:

  • AT+ - 所有命令所需的前导码。
  • CIPSERVER= 开始(或停止)侦听指定的网络端口。即启动(或停止)服务/服务器。

1、--开始监听(使用0,停止监听)
80 - 我们想要侦听传入连接请求的网络端口 - 特别是端口 80。您可以在此处使用任何有效的端口号。请注意,许多端口号通常用于特定功能(端口 80 通常由 Web 服务器使用),如这篇Wikipedia 文章 中所述。
因此,从上一步中,我们知道连接到 Arduino 所需的 IP 地址(我的是 192.168.3.160),现在我们也知道在建立连接时必须使用端口 80。

以下使用 netcat 的脚本显示了使用我的 IP 地址与 Arduino 的连接。您需要将 192.168.3.160 替换为您的 IP 地址。如果您还没有,可以在步骤 1 的底部找到有关获取 netcat 的信息。

在 Shell(Mac、Linux 或 Cygwin)提示符或 Windows 命令提示符中输入以下内容。请记住,使用您的 IP 地址代替 192.168.3.160。

如果一切顺利,此时 netcat 中不会发生任何其他事情。所有的动作都在 Arduino 上结束。在串行监视器中,您应该看到如下内容以响应 netcat 命令:

这意味着一个客户端已经连接,并且它被分配了会话 ID 0。如果另一个客户端连接,您将看到“1,CONNECT”形式的消息。这意味着客户端已连接并将被分配会话 ID 1。

接下来,翻回 netcat 并输入文本“1+”(无引号)并按 Enter。您的终端会话现在应如下所示:再一次,不是非常令人兴奋,但再一次,Arduino 串行监视器和面包板上的动作都结束了。如果一切顺利,连接到 DIO 端口 5 的 LED 将亮起,您将在串行监视器中看到类似以下内容。

文本“+IPD,0,3:1+”是 ESP WiFi 发送给 Arduino 的内容。这可以解释如下:

+IPD - 已从客户端接收到传入数据。
0 - 数据是从会话 ID 为 0 的客户端接收的。
3: - 已从客户端接收到三个字节的数据。实际数据跟在“:”字符之后。
1+(后跟一个不显示的换行符)是来自客户端的数据 - 即您输入到 netcat 中的数据。
Arduino 程序对数据的解释如下:

+ 打开 LED(或 - 关闭 LED)
1(或 2 或 3)打开第 1 个 (DIO 5)、第 2 个 (DIO 6) 或第 3 个 (DIO 7) LED。
如果需要,您可以通过在 Arduino 串行监视器中输入 AT+CIPSEND= 命令将数据发送回 netcat。请注意,需要两行输入。第一个是将一些数据连同数据长度一起发送到会话 0 的命令,然后是要发送的实际文本(将给出“>”提示)。

在串行监视器的输出中,您应该看到如下内容:AT+CIPSEND=0,5 命令的细分是请求向会话 ID 0 标识的客户端发送 5 个字节的数据。

在您的计算机上的 netcat 会话中,您应该看到如下内容:

第 6 步:发布您的应用

您已经完成了构建应用程序的艰苦工作,现在您可以发布它了。您想将其移动到(物理上)较小的平台,例如 Arduino Uno,甚至是自定义电路。所以现在我们需要将代码迁移到该硬件。

这是一个可选步骤,但这是本文的全部内容。具体来说,如何相对轻松地开发您的 Arduino WiFi 应用程序,并在完成后将其移至较小的平台。一个平台,如果您用于原始开发,则需要您在每次上传代码时不断切换开关,在您想要运行它时重置这些开关以及将需要额外的硬件来观察调试消息。实际上,由于我在 WiFi Shield 和 Arduino 无法正常通信时遇到的一些头痛问题,因此编写这一步比其他所有步骤加起来花费的时间更长,因此不得不通过将 Uno 上的 DIP 开关切换为单调乏味的方式进行调试试着弄清楚发生了什么。问题是 ESP 软件以某种方式自我损坏并不断重启。解决方案是重新映像 ESP 模块。

请注意,您可能仍然会像我一样在较小的硬件平台上遇到问题,这需要一些故障排除以及我刚刚提到的“痛苦的事情”,但在这个阶段,大部分开发工作已经完成。因此,与在较小平台上进行完整开发的痛苦相比,最终的故障排除痛苦或至少应该大大减少。

在这一步中,我们将我们的应用程序迁移到带有 WiFi 屏蔽的 Arduino Uno。但是,您也可以使用 Arduino Uno+WiFi 集成板,例如 Jaycar XC-4411或其他组合。子步骤大致如下:

将我们的测试电路连接到 Uno + WiFi 板。
将代码上传到我们的 Arduino Uno + WiFi。
计算分配给此设备的 IP 地址(此步骤)
享受您闪亮的新应用程序,让这些 LED 发光!
连接测试电路
对于此子步骤,只需按照步骤 1 中所述将测试电路连接到 UNO。连接与步骤 1 中所述相同。具体而言,将 3 个 LED 连接到 DIO 引脚 5、6 和 7。然后连接地线到 Arduino 板上的 GND 引脚之一。

上传代码 - MCU 到 USB
此子步骤(“上传代码”)和以下子步骤(“运行代码”)对时间至关重要,因此请在开始之前完整阅读这两个步骤。

对于这一步,我假设您没有注释掉设置操作模式和加入 WiFi 的代码(第 4 步)。如果这样做,请在继续之前取消注释。使用Uno成功加入WiFi后,可以重新注释掉工作模式的设置(AT+CWMODE=1)和WiFi的加入(AT+CWJAP="...",".. .”),因为即使断电,ESP 也会记住这些设置。同样,我建议不要对此进行评论,但如果您真的想要,请在新硬件上成功运行后进行评论。

在 Uno 上,唯一的硬件串行端口用于上传代码、与串行监视器交互- 或 -与 ESP 通信 - 但并非同时进行。这是一个冲突。为了解决这个冲突,我们需要使用 WiFi Sheild(或 Uno+WiFi)板上的 DIP 开关告诉电路板我们正在做什么(上传或与 ESP 对话)。

简而言之,我们设置开关以允许通过 USB 上传 Arduino 代码。上传完全完成后,我们需要重置开关以允许 Arduino 与 ESP 通信(这是时间关键位)。

要设置上传开关,请握住Arduino Uno + ESP WiFi 扩展板,使 USB 位于左侧,您会注意到电路板右上角的 DIO 引脚 1 和 2 附近有 2 个 DIP 开关。将这两者都切换到关闭位置。参考图中蓝色电路板仅包含 2 个 DIP 开关。

如果您使用的是Uno + WiFi集成板,则在与集成Mega + WiFi板上的位置类似的位置有一个8路拨码开关。请参阅带有包含 8 个 DIP 开关的黑色电路板的图像。将代码上传到 Arduino 时,您必须按如下方式切换开关:

开关 1 和 2 - 关闭
开关 3 和 4 -打开
开关 5、6、7 和 8 - 关闭。
以正常方式上传代码。除非打开调试模式,否则您将看不到与发送 AT+GMR 命令相关的任何内容 - 然而,它已经发送,Arduino 将等待回复 - 永远不会到来。我们需要在 Arduino 超时等待 AT+GMR 响应之前将 DIP 开关重置为运行代码模式。这是时间关键点,所以请继续阅读......

运行代码 - MCU 到 ESP
在 Arduino 等待 AT+GMR 响应超时之前,我们需要重新配置开发板以运行代码。作为替代方案,您可以按照此处所述设置开关并重新启动 Arduino(使用 Arduino 重置,而不是 ESP 重置 - 或者只是将其关闭然后再次打开)。

如果您使用的是 Arduino Uno + WiFi 扩展板,请将上面提到的 2 个 DIP 开关转到打开位置。

如果您使用的是集成的 Arduino Uno + WiFi 板,请按如下方式设置 8 个 DIP 开关块:

  • 开关 1 和 2 -打开
  • 开关 3 和 4 - 关闭
  • 开关 5、6、7 和 8 - 关闭 - 即与前一个子步骤没有变化。

如果您足够快,或者您使用了替代方案(设置开关并重新启动),您的 Arduino 应该加入您的 WiFi 网络。
最后一个 Arduino 子步骤是访问我们的 Arduino LED 服务。为此,您需要找出分配给您设备的 IP 地址。

就我而言,我可以访问 WiFi 路由器管理控制台。我只是记下分配地址列表中出现的新设备(有点像确定 Arduino 在 Arduino IDE 中分配的通信端口的过程)。这相对容易,因为对于我拥有的 WiFi 模块,系统名称要么是 WiFi 硬件的 MAC 地址,要么是名称中包含字母 ESP。这两个示例可以从我的 WiFi 路由器管理控制台的屏幕截图中看到。

注意:有时这里的内嵌图片似乎消失了,但我已将它们包含在此步骤的标题中。

请注意,第一个包含 Mega (192.168.3.160) 的 IP 地址,因此另一个 (192.168.3.94) 是 Uno。

和以前一样,我们可以使用 netcat 在端口 80 上联系 Uno 并发出命令来点亮或关闭 LED。

第 7 步:后续步骤

希望本文对您有所帮助,并使您能够轻松构建基于 Arduino 的 WiFi 应用程序。如果你这样做了,请点击“我构建了这个”链接让我知道。还有任何可以改进文章的反馈——例如,根据您在尝试自己做这件事时所面临的经验和挑战,我们将不胜感激。

正如开场白中提到的,当与其他使用串行通信的设备(例如蓝牙模块)一起工作时,也可以使用这种技术。

最好的下一步是将这个基础提升到一个新的水平。

向 Arduino 代码添加功能。打开或关闭 LED 的过程全部在 processInput(...) 函数中处理,大约在主 Arduino 代码的第 166 行。

在这个 processInput(...) 函数中,您将在大约第 175 行看到以下代码块:

  if (strncmp(msg, "+IPD", 4) == 0) {
    HOST.println(F("Received input from client"));
    char led = msg[9];
    char setting = msg[10];
    if (led >= '1' && led <= '3') {
      led = led - '1';
    } else {
      HOST.print(F("Invalid LED: ")); HOST.println(led);
      return;
    }

    /************************************* 
     *  Comment out/Uncomment this block of print
     *  statements to represent out "development cycle".
     *************************************/
    DEBUG.print(F("Setting LED ")); DEBUG.print(led);
      DEBUG.print(F(" on DIO ")); DEBUG.print(led + 5);
      DEBUG.println(setting == "+" ? F(" on") : F(" off"));

    digitalWrite(led + 5, setting == '+');
  }

首先识别消息是从客户端输入的。如果传入的消息 (msg) 以“+IPD”开头,那么我们就知道它是来自客户端的消息。

if 块的其余部分处理消息。注释解释了正在发生的事情,因此我建议您参阅代码以进行解释。

您可以更改此设置以识别和处理您自己的消息。当您这样做时,您可以控制您想要的任何内容,包括将消息发送回客户端。

最后,程序如何在不将所有调试和状态消息发送到 WiFi 的情况下从 Mega 移植到 Uno。记住:

Mega 使用 Serial 3 进行 WiFi 交互,使用 Serial(0) 进行调试/状态消息。
Uno 使用 Serial(0) 进行 WiFi 交互,因此无法生成调试/状态消息。
诀窍在于使用常量 DEBUG、HOST(可以在上面的代码中看到)和 ESP(可以在程序的其余部分看到)。

这些常量是使用条件编译预处理器指令(即编译器#if 语句)在程序顶部附近定义的。这出现在主要 Arduino 代码的第 79 到 116 行。

在为 Mega 编译时,DEBUG 和 HOST 定义为 Serial(0),ESP 定义为 Serial3。

为 Uno 编译时,DEBUG 和 Host 定义为 NullSerial(即对我们在步骤 3 中创建的额外文件中的代码的引用),ESP 定义为 Serial(0)。

在这两种情况下,都定义了一些附加常数(例如引脚数和波特率)来定义操作环境。

因此,当在 Uno 上运行时,调试和状态消息会转到这个 NullSerial 对象。NullSerial 是一个 C++ 类,它定义了一些与 Arduino 提供的真正串行设备定义类似的方法集。例如,NullSerial 有一个 println() 方法。NullSerial 的 println 方法什么都不做。因此,发送到 DEBUG 和 HOST 的调试和状态消息会被简单地丢弃,因为它们无处可发送。

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论