代码之家  ›  专栏  ›  技术社区  ›  Codebeat

ARDIONI:C++:f-(宏)在函数内有意义吗?

  •  1
  • Codebeat  · 技术社区  · 6 年前

    f()宏可用于将全局变量存储在程序内存(闪存)中,而不是动态工作内存中,因此还有更多的可用内存。

    然而,我在ESP8266图书馆看到了这个杂乱无章的例子。它工作得很好,但是我对函数中的f()宏的用法有一些疑问。在函数内部使用它有用吗?


    示例代码:

    #include <ESP8266WiFi.h>
    #include <WiFiClient.h>
    #include <ESP8266WebServer.h>
    #include <DNSServer.h>
    #include <ESP8266mDNS.h>
    #include <EEPROM.h>
    
    /*
       This example serves a "hello world" on a WLAN and a SoftAP at the same time.
       The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM.
       Connect your computer or cell phone to wifi network ESP_ap with password 12345678. A popup may appear and it allow you to go to WLAN config. If it does not then navigate to http://192.168.4.1/wifi and config it there.
       Then wait for the module to connect to your wifi and take note of the WLAN IP it got. Then you can disconnect from ESP_ap and return to your regular WLAN.
       Now the ESP8266 is in your network. You can reach it through http://192.168.x.x/ (the IP you took note of) or maybe at http://esp8266.local too.
       This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/
    */
    
    /* Set these to your desired softAP credentials. They are not configurable at runtime */
    #ifndef APSSID
    #define APSSID "TheGeekMan"
    #define APPSK  "12345678"
    #endif
    
    const char *softAP_ssid = APSSID;
    const char *softAP_password = APPSK;
    
    /* hostname for mDNS. Should work at least on windows. Try http://esp8266.local */
    const char *myHostname = "thegeekman";
    
    /* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
    char ssid[32] = "";
    char password[32] = "";
    
    // DNS server
    const byte DNS_PORT = 53;
    DNSServer dnsServer;
    
    // Web server
    ESP8266WebServer server(80);
    
    /* Soft AP network parameters */
    //IPAddress apIP(192, 168, 4, 1);
    IPAddress apIP(8, 8, 8, 8);
    IPAddress netMsk(255, 255, 255, 0);
    
    
    /** Should I connect to WLAN asap? */
    boolean connect;
    
    /** Last time I tried to connect to WLAN */
    unsigned long lastConnectTry = 0;
    
    /** Current WLAN status */
    unsigned int status = WL_IDLE_STATUS;
    
    /** Is this an IP? */
    boolean isIp(String str) {
      for (size_t i = 0; i < str.length(); i++) {
        int c = str.charAt(i);
        if (c != '.' && (c < '0' || c > '9')) {
          return false;
        }
      }
      return true;
    }
    
    /** IP to String? */
    String toStringIp(IPAddress ip) {
      String res = "";
      for (int i = 0; i < 3; i++) {
        res += String((ip >> (8 * i)) & 0xFF) + ".";
      }
      res += String(((ip >> 8 * 3)) & 0xFF);
      return res;
    }
    
    /** Load WLAN credentials from EEPROM */
    void loadCredentials() {
      EEPROM.begin(512);
      EEPROM.get(0, ssid);
      EEPROM.get(0 + sizeof(ssid), password);
      char ok[2 + 1];
      EEPROM.get(0 + sizeof(ssid) + sizeof(password), ok);
      EEPROM.end();
      if (String(ok) != String("OK")) {
        ssid[0] = 0;
        password[0] = 0;
      }
      Serial.println("Recovered credentials:");
      Serial.println(ssid);
      Serial.println(strlen(password) > 0 ? "********" : "<no password>");
    }
    
    /** Store WLAN credentials to EEPROM */
    void saveCredentials() {
      EEPROM.begin(512);
      EEPROM.put(0, ssid);
      EEPROM.put(0 + sizeof(ssid), password);
      char ok[2 + 1] = "OK";
      EEPROM.put(0 + sizeof(ssid) + sizeof(password), ok);
      EEPROM.commit();
      EEPROM.end();
    }
    
    /** Handle root or redirect to captive portal */
    void handleRoot() {
      if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
        return;
      }
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "-1");
    
      String Page;
      Page += F(
                "<html><head></head><body>"
                "<h1>HELLO WORLD!!</h1>");
      if (server.client().localIP() == apIP) {
        Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
      } else {
        Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
      }
      Page += F(
                "<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
                "</body></html>");
    
      server.send(200, "text/html", Page);
    }
    
    /** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
    boolean captivePortal() {
      if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) {
        Serial.println("Request redirected to captive portal");
        server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
        server.send(302, "text/plain", "");   // Empty content inhibits Content-length header so we have to close the socket ourselves.
        server.client().stop(); // Stop is needed because we sent no content length
        return true;
      }
      return false;
    }
    
    /** Wifi config page handler */
    void handleWifi() {
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "-1");
    
      String Page;
      Page += F(
                "<html><head></head><body>"
                "<h1>Wifi config</h1>");
      if (server.client().localIP() == apIP) {
        Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
      } else {
        Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
      }
      Page +=
        String(F(
                 "\r\n<br />"
                 "<table><tr><th align='left'>SoftAP config</th></tr>"
                 "<tr><td>SSID ")) +
        String(softAP_ssid) +
        F("</td></tr>"
          "<tr><td>IP ") +
        toStringIp(WiFi.softAPIP()) +
        F("</td></tr>"
          "</table>"
          "\r\n<br />"
          "<table><tr><th align='left'>WLAN config</th></tr>"
          "<tr><td>SSID ") +
        String(ssid) +
        F("</td></tr>"
          "<tr><td>IP ") +
        toStringIp(WiFi.localIP()) +
        F("</td></tr>"
          "</table>"
          "\r\n<br />"
          "<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>");
      Serial.println("scan start");
      int n = WiFi.scanNetworks();
      Serial.println("scan done");
      if (n > 0) {
        for (int i = 0; i < n; i++) {
          Page += String(F("\r\n<tr><td>SSID ")) + WiFi.SSID(i) + ((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? F(" ") : F(" *")) + F(" (") + WiFi.RSSI(i) + F(")</td></tr>");
        }
      } else {
        Page += F("<tr><td>No WLAN found</td></tr>");
      }
      Page += F(
                "</table>"
                "\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
                "<input type='text' placeholder='network' name='n'/>"
                "<br /><input type='password' placeholder='password' name='p'/>"
                "<br /><input type='submit' value='Connect/Disconnect'/></form>"
                "<p>You may want to <a href='/'>return to the home page</a>.</p>"
                "</body></html>");
      server.send(200, "text/html", Page);
      server.client().stop(); // Stop is needed because we sent no content length
    }
    
    /** Handle the WLAN save form and redirect to WLAN config page again */
    void handleWifiSave() {
      Serial.println("wifi save");
      server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
      server.arg("p").toCharArray(password, sizeof(password) - 1);
      server.sendHeader("Location", "wifi", true);
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "-1");
      server.send(302, "text/plain", "");    // Empty content inhibits Content-length header so we have to close the socket ourselves.
      server.client().stop(); // Stop is needed because we sent no content length
      saveCredentials();
      connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
    }
    
    void handleNotFound() {
      if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
        return;
      }
      String message = F("File Not Found\n\n");
      message += F("URI: ");
      message += server.uri();
      message += F("\nMethod: ");
      message += (server.method() == HTTP_GET) ? "GET" : "POST";
      message += F("\nArguments: ");
      message += server.args();
      message += F("\n");
    
      for (uint8_t i = 0; i < server.args(); i++) {
        message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\n");
      }
      server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
      server.sendHeader("Pragma", "no-cache");
      server.sendHeader("Expires", "-1");
      server.send(404, "text/plain", message);
    }
    
    void setup() {
      delay(1000);
      Serial.begin(9600);
      Serial.println();
      Serial.println("Configuring access point...");
      /* You can remove the password parameter if you want the AP to be open. */
      WiFi.softAPConfig(apIP, apIP, netMsk);
      WiFi.softAP(softAP_ssid, softAP_password);
      delay(500); // Without delay I've seen the IP address blank
      Serial.print("AP IP address: ");
      Serial.println(WiFi.softAPIP());
    
      /* Setup the DNS server redirecting all the domains to the apIP */
      dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
      dnsServer.start(DNS_PORT, "*", apIP);
    
      /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
      server.on("/", handleRoot);
      server.on("/wifi", handleWifi);
      server.on("/wifisave", handleWifiSave);
      server.on("/generate_204", handleRoot);  //Android captive portal. Maybe not needed. Might be handled by notFound handler.
      server.on("/fwlink", handleRoot);  //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
      server.onNotFound(handleNotFound);
      server.begin(); // Web server start
      Serial.println("HTTP server started");
      loadCredentials(); // Load WLAN credentials from network
      connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID
    }
    
    void connectWifi() {
      Serial.println("Connecting as wifi client...");
      WiFi.disconnect();
      WiFi.begin(ssid, password);
      int connRes = WiFi.waitForConnectResult();
      Serial.print("connRes: ");
      Serial.println(connRes);
    }
    
    void loop() {
      if (connect) {
        Serial.println("Connect requested");
        connect = false;
        connectWifi();
        lastConnectTry = millis();
      }
      {
        unsigned int s = WiFi.status();
        if (s == 0 && millis() > (lastConnectTry + 60000)) {
          /* If WLAN disconnected and idle try to connect */
          /* Don't set retry time too low as retry interfere the softAP operation */
          connect = true;
        }
        if (status != s) { // WLAN status change
          Serial.print("Status: ");
          Serial.println(s);
          status = s;
          if (s == WL_CONNECTED) {
            /* Just connected to WLAN */
            Serial.println("");
            Serial.print("Connected to ");
            Serial.println(ssid);
            Serial.print("IP address: ");
            Serial.println(WiFi.localIP());
    
            // Setup MDNS responder
            if (!MDNS.begin(myHostname)) {
              Serial.println("Error setting up MDNS responder!");
            } else {
              Serial.println("mDNS responder started");
              // Add service to MDNS-SD
              MDNS.addService("http", "tcp", 80);
            }
          } else if (s == WL_NO_SSID_AVAIL) {
            WiFi.disconnect();
          }
        }
        if (s == WL_CONNECTED) {
          MDNS.update();
        }
      }
      // Do work:
      //DNS
      dnsServer.processNextRequest();
      //HTTP
      server.handleClient();
    }
    

    函数存储在程序空间(程序内存)中,因此该函数中的字符串也被编译到程序空间中。那么,当通常已经从程序空间加载字符串时,为什么要使用f()宏强制将字符串存储在程序空间中呢?我认为这是不必要的开销,或者这样做有什么好处?


    例如,在源代码中,您可以在函数中找到许多这样的赋值:

    void handleWifi()
    {
      String Page;
      Page+= F("<html><head></head><body>"
               "<h1>Wifi config</h1>");
      .........
      .........
    }
    

    这里将发生的是字符串从程序空间加载到动态内存中(作为变量“page”)。那么这里真正的交易是什么呢?不过,我认为C++编译器在做一个简单的赋值时会做得更好(优化)。

    我是对的还是错的?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Codebeat    6 年前

    好吧,我是 错误的 ,抱歉,函数内部的f()也有意义。为了证明这一点,我做了一些测试。

    使用这个简单的草图进行了一些测试(取消对要测试的草图的注释):

    // Printing 33 chars
    // 1.
    //void printStr() { Serial.println( "0123456789ABCDEFGHo!@#$%^&*()_+<>?" ); }
    //void printStr() { Serial.println( F("0123456789ABCDEFGHo!@#$%^&*()_+<>?" )); }
    // 2.
    void printStr() { String s = "0123456789ABCDEFGHo!@#$%^&*()_+<>?"; Serial.println( s ); }
    //void printStr() { String s; s+=F("0123456789ABCDEFGHo!@#$%^&*()_+<>?"); Serial.println( s ); }
    
    void setup() {
      Serial.begin(9600);
    }
    
    void loop() {
     printStr();
     delay(1000);
    }
    

    结果:

    没有 使用 F-() 26816字节

    The sketch uses 263136 bytes (25%) of program storage space. Maximum is 1044464 bytes.
    Global variables use 26816 bytes (32%) of the dynamic memory. 
    Remain 55104 bytes for local variables. Maximum is 81920 bytes.
    

    使用 F-() 26788字节

    The sketch uses 263212 bytes (25%) of program storage space. Maximum is 1044464 bytes.
    Global variables use 26788 bytes (32%) of the dynamic memory. 
    Remain 55132 bytes for local variables. Maximum is 81920 bytes.
    

    -与f()有28个字节的差异。但好吧,现在我们确定了!;-)


    更新日期:2019年1月11日

    背景信息为什么这样工作:

    为了不同的目的,RAM被分成不同的块。存在一个块,其中存储所有全局和静态变量(即BSS和数据区域)。其中有存储函数中创建的局部变量的堆栈,最后还有存储动态变量的堆。

    如果你想更多地了解这些记忆块是如何相互关联的,你可以阅读更多的内容。 Wikipedia .


    从此处复制的信息 article .

        2
  •  0
  •   Homper    6 年前

    可能是因为这里描述的原因: http://playground.arduino.cc/Learning/Memory

    闪存(程序)内存比SRAM可用内存多得多 使用Arduino语言创建变量时,例如:

    char message[] = "I support the Cape Wind project.";

    您正在从复制33个字节(1个字符=1个字节,加上终止空值) 在使用SRAM之前将内存编程到SRAM中。33字节不是很多 内存在一个1024字节的池中,但是如果草图需要一些大的 不变的数据结构-例如要发送到的大量文本 例如,使用闪存的显示器或大型查找表 (程序存储器)直接存储可能是唯一的选择。做 使用progmem关键字。