Geheugengebruik verminderen Veel dobbers

Ik gebruik een Arduino Uno - Alle code op github.com/robbrad/AirQuality

Huidig ​​schetsgebruik:

Sketch uses 23186 bytes (71%) of program storage space. Maximum is 32256 bytes.
Global variables use 1428 bytes (69%) of dynamic memory, leaving 620 bytes for local variables. Maximum is 2048 bytes.

Normaal zou ik niet alleen spam-code gebruiken voor een optimalisatie, maar ik ben op zoek naar ongeveer 10% geheugen terug van deze schets

Het controleert in feite een aantal sensoren die de waarden instellen op drijvers, een tekenreeks maken en die naar een php-pagina posten waar wat meer verwerking plaatsvindt.

Ik heb alles geprobeerd voor zover ik weet, maar ik kan niet denken aan een slimmere manier om minder geheugen te gebruiken.

Ik heb een array geprobeerd, een functie om de datastring te bouwen, een float gebruiken en deze aan de tekenreeks koppelen wanneer deze is ingesteld

Ik dacht er zelfs aan om de Math.h te beperken tot de wiskundige functies die ik gebruikte

Misschien ben ik gewoon de grenzen van de kleine planken aan het halen (wat toch behoorlijk goed is :)! )

#include 
#include 
#include "DHT.h"
#include 
#include 
#include "MutichannelGasSensor.h"
#include "HP20x_dev.h"
#include "KalmanFilter.h"

unsigned char ret = 0;

/* Instance */
KalmanFilter t_filter;    //temperature filter
KalmanFilter p_filter;    //pressure filter
KalmanFilter a_filter;    //altitude filter

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:
IPAddress server(192, 168, 0, 30);//numeric IP for Google (no DNS)
//char server[] = "www.google.com";   //name address for Google (using DNS)

// Set the static IP address to use if the DHCP fails to assign
IPAddress ip(192, 168, 0, 40);

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
EthernetClient client;


#define Vc 4.95
//the number of R0 you detected just now
#define R0 35.54
#define DHTPIN A1    //what pin we're connected to

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11  //DHT 11
#define DHTTYPE DHT22  //DHT 22  (AM2302)
//#define DHTTYPE DHT21  //DHT 21 (AM2301)

DHT dht(DHTPIN, DHTTYPE);

int pin = 8;
unsigned long duration;
unsigned long starttime;
unsigned long sampletime_ms = 2000;//sampe 30s ;
unsigned long lowpulseoccupancy = 0;
float ratio = 0;
float concentration = 0;
String data = "";

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ;//wait for serial port to connect. Needed for native USB port only
  }

  delay(150);
  HP20x.begin();
  delay(100);

  /* Determine HP20x_dev is available or not */
  ret = HP20x.isAvailable();

  pinMode(8, INPUT);
  starttime = millis();//get the current time;
  //Serial.println("power on!");


  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);

  //if (Ethernet.begin(mac) == 0) {
 // Serial.println("Failed to configure Ethernet using DHCP");
 //try to congifure using IP address instead of DHCP:
  Ethernet.begin(mac, ip);

 //give the Ethernet shield a second to initialize:
  delay(10000);
 //Serial.println("connecting...");
  //}
 //give the Ethernet shield a second to initialize:
  //delay(1000);
  //Serial.println("connecting...");



 //Serial.println(Ethernet.localIP());


  gas.begin(0x04);//the default I2C address of the slave is 0x04
  gas.powerOn();
  //Serial.print("Firmware Version = ");
  //Serial.println(gas.getVersion());
  //Serial.println("Particles\tRS\tHCHO (PPM)\tNH3 (PPM)\tCO (PPM)\tNO2     (PPM)\tC3H8 (PPM)\tC4H10 (PPM)\tCH4 (PPM)\tH2 (PPM)\tC2H5OH (PPM)");
  dht.begin();
  delay(10000);

}

void loop() {

  if (client.available()) {
    char c = client.read();
   // Serial.print(c);
  }

  float HATemp2;
  float HAPres2;
  float HAAlt2;
  float d;

  if (OK_HP20X_DEV == ret)
  {
    unsigned long HATemp = HP20x.ReadTemperature();
    d = HATemp/100.0;
    HATemp2 = t_filter.Filter(d);

    unsigned long HAPres = HP20x.ReadPressure();
    d = HAPres/100.0;
    HAPres2 = p_filter.Filter(d);

    unsigned long HAAlt = HP20x.ReadAltitude();
    d = HAAlt/100.0;
    HAAlt2 = a_filter.Filter(d);

  }

  duration = pulseIn(pin, LOW);
  lowpulseoccupancy = lowpulseoccupancy + duration;



  if ((millis() - starttime) >= sampletime_ms) //if the sampel time = = 30s
  {
    ratio = lowpulseoccupancy/(sampletime_ms * 10.0);//Integer percentage 0=>100

    d = 1.1 * pow(ratio, 3) - 3.8 * pow(ratio, 2) + 520 * ratio + 0.62;//using spec sheet curve
    //Serial.print("concentration = ");



   lowpulseoccupancy = 0;
    starttime = millis();
  }



 //Serial.print("\t");
  //HCHO

  int sensorValue = analogRead(A0);
  double Rs = (1023.0/sensorValue) - 1;

  double ppm = pow(10.0, ((log10(Rs/R0) - 0.0827)/(-0.4807)));
  //Serial.print("HCHO ppm = ");

  //MultiChannel Gas
  float NH3 = gas.measure_NH3();
  float CO = gas.measure_CO();
  float NO2 = gas.measure_NO2();
  float C3H8 = gas.measure_C3H8();
  float C4H10 = gas.measure_C4H10();
  float CH4 = gas.measure_CH4();
  float H2 = gas.measure_H2();
  float C2H5OH = gas.measure_C2H5OH();


 //Reading temperature or humidity takes about 250 milliseconds!
 //Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  data = String("dust=") + d + "&rs=" + Rs + "&hcho=" + ppm + "&nh3=" + NH3 + "&co=" + CO + "&no2=" + NO2 + "&c3h8=" + C3H8 + "&c4h10=" + C4H10 + "&ch4=" + CH4 + "&h2=" + H2 + "&c2h5oh=" + C2H5OH + "&temp=" + t + "&hum=" + h + "&HATemp=" + HATemp2 + "&HAPres=" + HAPres2 + "&HAAlt=" + HAAlt2;

  //Serial.println(data);


  if (client.connect(server, 80)) {
    Serial.println(F("connected"));
    client.println(F("POST /air_add.php HTTP/1.1"));
    client.println(F("Host:  192.168.0.30"));
    client.println(F("User-Agent: Arduino/1.0"));
    client.println(F("Connection: close"));
    client.println(F("Content-Type: application/x-www-form-urlencoded;"));
    client.print(F("Content-Length: "));
    client.println(data.length());
    client.println();
    client.println(data);
  }
  else
  {
    Serial.println(F("could not connect"));
  }

  Serial.println(data);

  if (client.connected()) {
    client.stop();
  }


  delay(300000);
}
2
Is this uw KalmanFilter ? Ik kan er niet helemaal logisch uitzien. Voor mij ziet het er vreemd en erg geheugenintensief uit. Kun je in plaats daarvan geen basis eerste-orde low-pass gebruiken?
toegevoegd de auteur Sprogz, de bron
Waarom wil je 10% terug krijgen? Zelfs als het programmageheugen voor 99% gevuld is, zal het geen problemen veroorzaken, tenzij je meer dingen wilt toevoegen. Zelfs het gebruik van 70% RAM is niet schandalig. De linker heeft de neiging om zwaar te optimaliseren, dus het is (waarschijnlijk) alleen inclusief de routines waarnaar je echt verwijst.
toegevoegd de auteur Nick Gammon, de bron
U hebt niet geantwoord op de eerste opmerking. Wilt u RAM of PROGMEM opslaan? Ze hebben heel andere doelen. Mogelijk hebt u heap-fragmentatie vanwege uw gebruik van de klasse String die ik u aanraden te stoppen met gebruiken (zoals anderen hebben geadviseerd).
toegevoegd de auteur Nick Gammon, de bron
Zelfs als u 99,9% van PROGMEM hebt gebruikt, heeft dat geen invloed op de stabiliteit. Echter met string-fragmentatie (met name veroorzaakt door de klasse String) zou zelfs 50% van het RAM-geheugen te veel kunnen zijn. Het lijkt erop dat je het probleem hebt gevonden.
toegevoegd de auteur Nick Gammon, de bron
Uw gebruik van de macro F (bijv. Serial.println (F ("connected")); ) is zeer verstandig. Zonder dit kopieert de compiler de string naar het RAM-geheugen, waardoor de beschikbare RAM verder wordt verkleind. Gedaan met de macro F genereert de compiler code om de constante gegevens rechtstreeks uit PROGMEM te halen.
toegevoegd de auteur Nick Gammon, de bron
Het is een Uno. De compiler klaagde niet, maar het is goed voor respectievelijk 70% en 69% limi voor beide. Ik keek even naar de bibliotheken maar zag niets dat naar me uitsprong. Misschien maak ik een schets met alleen de bibliotheken om alleen hun gebruik te zien
toegevoegd de auteur Christian, de bron
@Peter, ook mijn beste, ik ben meer een man van alle markten. Maar goed, ik realiseer me snel dat het een heel ander gebied is om in te werken en niet alle aanvankelijk geleerde principes zijn van toepassing
toegevoegd de auteur Christian, de bron
@NickGammon +1 jouw plekje aan. Dit lijkt de oorzaak van het probleem te zijn. Ik onderzoek nu alternatieve methoden voor het verzenden van de gegevens
toegevoegd de auteur Christian, de bron
@ EdgarBonet - ja dat is het, ik heb hier geen ervaring mee - het werd aanbevolen als onderdeel van de sensorimplantatie
toegevoegd de auteur Christian, de bron
@NickGammon Ik ben gestopt met het gebruik van String en het lijkt de stabiliteit te hebben verbeterd - ik probeerde PROGMEM op te slaan
toegevoegd de auteur Christian, de bron
@NickGammon 10% was een willekeurig getal, omdat ik incidentele problemen op het oog kreeg toen ik dit niveau van gebruik bereikte, uiteraard is dat misschien niet gerelateerd. Het probleem dat ik zag was dat het soms alle waarden zou uitvoeren, behalve als het niets zou produceren.
toegevoegd de auteur Christian, de bron
Ik heb zojuist de cpp-bestanden toegevoegd
toegevoegd de auteur Christian, de bron
toegevoegd de auteur Christian, de bron
Bedankt, maar dat zijn alleen de * .h-bestanden, niet de gebruikte bibliotheken. Ik stel voor om de 'String'-klasse te verwijderen, en als dat niet genoeg is, vervang dan de Arduino Uno door een bord dat meer geheugen heeft (dat ga je waarschijnlijk toch op een dag doen).
toegevoegd de auteur Standback, de bron
Update uw vraag met de extra informatie. U kunt links toevoegen naar de gebruikte (niet-Arduino) bibliotheken, zodat we ze kunnen bekijken. Voor een Arduino Uno kun je maar beter het 'String'-object verwijderen. Ook al zou dat resulteren in 20 regels code of meer. Het Ethernet-schild wordt meestal gebruikt met een Arduino Mega 2560 of een Arduino Due, omdat de Uno niet genoeg geheugen heeft.
toegevoegd de auteur Standback, de bron
Voor welk Arduino-bord? Probeert u het RAM- of flash-geheugen te verminderen? Wat zegt de compiler over het RAM- en flash-geheugengebruik? Die paar zwevende variabelen op de stapel zijn niet het probleem. De bibliotheken kunnen veel geheugen gebruiken. Als geen enkele bibliotheek het 'String'-object gebruikt, moet u dat proberen te vermijden. U kunt ook blote sensorgegevens verzenden en de berekening en verwerking in de server uitvoeren.
toegevoegd de auteur Standback, de bron
@Beroven. Als je praat over PHP denk ik dat je een software-ontwikkelaar bent (zoals ik). Ik stel voor om de diff te bekijken. tussen de architectuur van Harvard en Von Neumann. Arduino is GEEN computer. Het optimaliseren van PHP-code geeft je meer ruimte (Von Neumann-architectuur), maar het optimaliseren van de schets zal je NIET meer ruimte voor variabele geven (Harvard-architectuur).
toegevoegd de auteur Peter, de bron

2 antwoord

Ik sluit het advies af dat je al is gegeven over vermijding String . Anders dan dat, zou het gebruik van fixed-point wiskunde een grote overwinning kunnen zijn, maar gezien de wiskunde die je doet, zal het niet gemakkelijk zijn.

Ervan uitgaande dat je vasthoudt aan zwevende punten, vond ik er gewoon een paar optimalisatiemogelijkheden:

  • Gebruik pow niet om een ​​polynoom te evalueren. Gebruik de Horner's methode die veel is goedkoper: d = ((1,1 * ratio - 3,8) * ratio + 520) * ratio + 0,62 .
  • De uitdrukking van ppm kan worden vereenvoudigd: ppm = 2500.33 * pow (Rs, -2.0803) .

Edit: You could also try to implement chunked transfer encoding. This way you could send the data to the server on the fly, as you collet it, instead of storing everything in memory and sending it in one go.

Voorbeeld:

void send_chunk(Print &connection, const String &chunk)
{
    connection.println(chunk.length, 16);
    connection.println(chunk);
}

void loop() {
   //...

   //last header sent to the client:
    client.println(F("Transfer-Encoding: chunked"));
    client.println();

   //Now the data:
    String chunk;
    chunk = "dust=";
    chunk += ((1.1*ratio - 3.8)*ratio + 520)*ratio + 0.62;
    send_chunk(client, chunk);
    chunk = "&rs=";
    chunk += Rs;
    send_chunk(client, chunk);
    chunk = "&hcho=";
    chunk += 2500.33 * pow(Rs, -2.0803);
    send_chunk(client, chunk);
    chunk = "&nh3=";
    chunk += gas.measure_NH3();
    send_chunk(client, chunk);
   //and so on...
    chunk = "";
    send_chunk(client, chunk); //Empty chunk means end of message.

   //...
}

Merk op dat er geen header "Content-Length" is. In plaats daarvan is elk stuk voorafgegaan door zijn grootte in hex. En, het allerbelangrijkst, merk dat op deze manier op je kunt veel variabelen kwijtraken.


Edit 2: I came out with a better (more generic) way to implement chunked encoding, which involves sending the data through a “chunker” filter that does the encoding and outputs to the client. Here is my Chunker class:

/*
 * Transfer a message using the HTTP/1.1 "Chunked Transfer Coding".
 * C.f. https://tools.ietf.org/html/rfc7230#section-4.1
 */
class Chunker : public Print
{
public:
    Chunker(Print &downstream) : downstream(downstream), pos(0) {}

    virtual size_t write(uint8_t c)
    {
        buffer[pos++] = c;
        if (pos == BUFFER_SIZE)
            send();
        return 1;
    }

    void end()
    {
        if (pos) send();
        send(); //empty chunk means end of message
    }

private:
    static const uint8_t BUFFER_SIZE = 32;
    Print &downstream;
    uint8_t buffer[BUFFER_SIZE];
    uint8_t pos;

   //Send the buffer as a chunk.
    void send()
    {
        downstream.println(pos, HEX);
        if (pos) {
            downstream.write(buffer, pos);
            downstream.println();
            pos = 0;
        }
    }
};

En hier is hoe je het zou gebruiken:

Chunker message(client); //instantiate the filter
message.print(F("dust="));
message.print(((1.1*ratio - 3.8)*ratio + 520)*ratio + 0.62);
message.print("&rs=");
message.print(Rs);
message.print(F("&hcho="));
message.print(2500.33 * pow(Rs, -2.0803));
message.print(F("&nh3="));
message.print(gas.measure_NH3());
// and so on...
message.end(); //terminate the chunked message

The main advantage of this method is that you completely avoid using the String class, which means no dynamic memory allocation (a very desirable thing). Also you can choose your buffer size. I've set it to 32 bytes, but you can make it smaller if needed.

2
toegevoegd
@Rob: ik weet niet veel over PHP. Weet niet zeker of je de codering zelf moet nemen. De link die u verstrekt is voor het toevoegen van gedeeltelijke ondersteuning aan PHP -streams , die een handig hulpmiddel zijn voor het afhandelen van zeer grote payloads. Je payload is klein, dus je hebt ze niet nodig. Ook neem ik aan dat uw webserver voor de decodeerstap zorgt, althans als mod_php wordt gebruikt. Houd er echter rekening mee dat er een PHP-fout is die voorkomt dat dit werkt in de snelle CGI-modus.
toegevoegd de auteur Sprogz, de bron
@Rob: hier is een meer generieke manier van het gebruik van gecodeerde codering, waarbij String wordt vermeden, of zelfs de noodzaak om te raden hoe u de benodigde marge hebt om sprintf() een getal te geven.
toegevoegd de auteur Sprogz, de bron
@Rob: ik heb nog een suggestie toegevoegd over het gebruik van het HTTP-protocol.
toegevoegd de auteur Sprogz, de bron
Dat is best verbazingwekkend ... Ik moet alleen de details uitwerken, zoals het verbindingsgedeelte. My Endpoint is een php-pagina/server die meer dingen laadt met het POST-verzoek - zou dit een goede plaats zijn om te starten dancingmammoth.com/2009/08/29/…
toegevoegd de auteur Christian, de bron
Dat is echt gaaf - bedankt dat je hier enige tijd aan hebt besteed - ik denk dat ik me aan deze aanpak waag
toegevoegd de auteur Christian, de bron
Fantastische suggesties! dank je
toegevoegd de auteur Christian, de bron

Twee basisbenaderingen:

1) gebruik wiskunde op vaste punten. of

1) gebruik int16_t of int32_t in plaats van de zwevende typen. u kunt bijvoorbeeld x10 of x100 gebruiken om variabelen met enkele decimale punten aan te duiden. gebruik ook x16 of x128 of zelfs x256 voor snelheidswinsten. je moet ervoor zorgen dat het niet overloopt.

bewerken: hier is een voorbeeld dat u hopelijk helpt om het op gang te krijgen.

void setup() {
    Serial.begin(9600);
    var1=100;               //var1=1.00
    var2=123;               //var2=1.23
}

void loop() {
    var1+=var2;             //increment var1
    Serial.print("var1 = "); Serial.print(var1/100); Serial.print("."); Serial.print(var1 - (var1/100) * 100); Serial.println(".");
    delay(100);
}

var1 (aanvankelijk 1.00) wordt verhoogd met var2 (1.23), beide typen int32_t.

hier is de afdruk.

enter image description here

2
toegevoegd
@dannyf Welke software gebruikt u op de laatste foto? Is dat een emulator?
toegevoegd de auteur Scott Langham, de bron
Dat is best gaaf, nog nooit eerder gedaan. Dus var1 is van het type int32_t. Dus wanneer het naar de string wordt gebracht, wordt het alleen op het punt naar de juiste precisie geconverteerd - is dit de essentie van wiskunde op een vast punt?
toegevoegd de auteur Christian, de bron
Oké, wat je zegt is dat ik int16_t kan gebruiken in plaats van een float? moet ik het resultaat op een andere manier casten? nu meer lezen
toegevoegd de auteur Christian, de bron
"is dit de essentie van wiskunde op vaste punten?" Ja. wiskundig vast punt is in basis 2, en wat ik heb getoond is in basis 10. met behulp van basis 2 versnelt de berekening en het gebruik van basis 10 is meer intuïtief.
toegevoegd de auteur dannyf, de bron
Dus, gebruik je int32_t in plaats van float? Beide zijn 4 bytes lang.
toegevoegd de auteur user31481, de bron