如何减少 Arduino Uno 上的内存使用

How to decrease memory usage on Arduino Uno

我正在使用 Arduino UNO、Dccduino 的克隆,但我遇到了问题 memory.Sketch 使用 25,114 字节 (77%) 的程序存储 space。最大值为 32,256 字节。全局变量使用 1,968 字节 (96%) 的动态内存,为局部变量留下 80 字节。最大值为 2,048 字节。可用内存不足,可能会出现稳定性问题。 有没有办法减少大约 20% 的内存,如果不是我想我必须购买 Arduino Mega

代码如下:

#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "RTClib.h"
#include <GPRS_Shield_Arduino.h>
#include <SoftwareSerial.h>

// Data wire is plugged into port 3 and 2 on the Arduino
#define ONE_WIRE_BUS_1 3  // Many sensors on pin 3 
#define ONE_WIRE_BUS_2 2 // Many sensors on pin 2
#define TEMPERATURE_PRECISION 9 // Lower resolution
#define PIN_TX    7
#define PIN_RX    8
#define BAUDRATE  9600
#define PHONE_NUMBER  "xxxxxxxxxxxxx"

GPRS gprsTest(PIN_TX, PIN_RX, BAUDRATE); //RX,TX,PWR,BaudRate

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire1(ONE_WIRE_BUS_1);
OneWire oneWire2(ONE_WIRE_BUS_2);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors1(&oneWire1);
DallasTemperature sensors2(&oneWire2);
int numberOfDevices1; // Number of temperature devices found on pin 3
int numberOfDevices2; // Number of temperature devices found on pin 2
DeviceAddress tempDeviceAddress1; // We'll use this variable to store a found device address for bus 3
DeviceAddress tempDeviceAddress2; // We'll use this variable to store a found device address for bus 2

File myFile;

RTC_DS3231 rtc; // Create a RealTimeClock object


void setup(void)
{

  // start serial port
#ifndef ESP8266
  while (!Serial); // for Leonardo/Micro/Zero
#endif
  Serial.begin(9600);
  delay(3000);
  Serial.println(F("Dallas Temperature IC Control Library Demo"));


  Serial.print( F("Initializing SD card..."));

  if (!SD.begin(4)) {
    Serial.println(F("\ninitialization failed!"));
    return;
  }
  Serial.println(F("initialization done."));

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }


  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

   /* while(!gprsTest.init()) { //gprs init
      delay(1000);
      Serial.print(F("init error\r\n")); 
      Serial.println(F("gprs init success"));*/ It takes 20% of dynamic memory so i cant use it
  }
  Serial.println(F("start to call ..."));// Call when device will start
  gprsTest.callUp(PHONE_NUMBER);
  Serial.println("start to send message ...");
  gprsTest.sendSMS(PHONE_NUMBER, "Hi device is ON"); //define phone number and text


  // Start up the library
  sensors1.begin();
  sensors2.begin();

  // Grab a count of devices on the wire
  numberOfDevices1 = sensors1.getDeviceCount();
  numberOfDevices2 = sensors2.getDeviceCount();

  // locate devices on the bus
  Serial.print(F("Locating devices..."));

  Serial.print(F("Found "));
  Serial.print(numberOfDevices1, DEC );
  Serial.print(F("+"));
  Serial.print(numberOfDevices2, DEC );
  Serial.println(F(" devices."));

  // report parasite power requirements
  Serial.print("Parasite power is: ");
  if (sensors1.isParasitePowerMode()) Serial.println(F("Sensors 1 ON"));
  else Serial.println(F("\nSensors 1 OFF"));
  if (sensors2.isParasitePowerMode()) Serial.println(F("Sensors 2 ON"));
  else Serial.println(F("Sensors 2 OFF"));


  // Loop through each device, print out address for pin 3
  for (int i = 0; i < numberOfDevices1; i++)
  {
    // Search the wire for address
    if (sensors1.getAddress(tempDeviceAddress1, i))
    {
      Serial.print(F("Found device "));
      Serial.print(i, DEC);
      Serial.print(F(" with address: "));
      printAddress(tempDeviceAddress1);
      Serial.println();
      Serial.println(F("\n"));
      // set the resolution to TEMPERATURE_PRECISION bit (Each Dallas/Maxim device is capable of several different resolutions)
      sensors1.setResolution(tempDeviceAddress1, TEMPERATURE_PRECISION);


    } else {
      Serial.print(F("Found ghost device for pin 3 at "));
      Serial.print(i, DEC);
      Serial.print(F(" but could not detect address. Check power and cabling"));
    }
  }


  // Loop through each device, print out address for pin 2
  for (int i = 0; i < numberOfDevices2; i++)
  {
    // Search the wire for address
    if (sensors2.getAddress(tempDeviceAddress2, i))
    {
      Serial.print(F("Found device "));
      Serial.print(i + numberOfDevices1, DEC);
      Serial.print(F(" with address: "));
      printAddress(tempDeviceAddress2);
      Serial.println();
      Serial.println(F("\n"));
      // set the resolution to TEMPERATURE_PRECISION bit (Each Dallas/Maxim device is capable of several different resolutions)
      sensors2.setResolution(tempDeviceAddress2, TEMPERATURE_PRECISION);

    } else {
      Serial.print(F("Found ghost device for pin 2 at "));
      Serial.print(i, DEC);
      Serial.print(F(" but could not detect address. Check power and cabling"));
    }
  }

}



void loop(void)
{
  // call sensors1.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  Serial.print(F("Requesting temperatures to pin 3..."));
  sensors1.requestTemperatures(); // Send the command to get temperatures for pin 3
  Serial.println(F("DONE"));

  myFile = SD.open("test1.txt", FILE_WRITE); //open file

  // Loop through each device , print out temperature data for pin 3
  for (int i = 0; i < numberOfDevices1; i++)
  {
    // Search the wire for address
    if (sensors1.getAddress(tempDeviceAddress1, i))
    {
      // Output the device ID
      Serial.print(F("Temperature for device: "));
      Serial.println(i, DEC);

      // It responds almost immediately. Let's print out the data

      printTemperature1(tempDeviceAddress1);// Use a simple function to print out the data

      Serial.print(F("\n"));
    }
    delay(4000);
    //else ghost device! Check your power requirements and cabling
  }// End forloop for pin 3
  if (numberOfDevices2 != 0) {
    Serial.print(F("Requesting temperatures to pin 2..."));
    sensors2.requestTemperatures(); // Send the command to get temperatures for pin 2
    Serial.println(F("DONE"));
  }


  // Loop through each device for pin 2, print out temperature data
  for (int i = 0; i < numberOfDevices2; i++)
  {
    // Search the wire for address
    if (sensors2.getAddress(tempDeviceAddress2, i))
    {
      // Output the device ID
      Serial.print(F("Temperature for device: "));
      Serial.println(i + numberOfDevices1, DEC);
      // It responds almost immediately. Let's print out the data
      printTemperature2(tempDeviceAddress2);// Use a simple function to print out the data
      Serial.print(F("\n"));
    }
    else Serial.print(F("ghost device! Check your power requirements and cabling"));
    delay(4000);
  } //End forloop for pin 3

  myFile.close(); // Should I close it?

}// End loop()






void printAddress(DeviceAddress deviceAddress) // function to print a device address
{
  for (uint8_t i = 0; i < 8; i++)
  {
    if (deviceAddress[i] < 16) Serial.print(F("0"));
    Serial.print(deviceAddress[i], HEX);
  }
}






void printTemperature1(DeviceAddress deviceAddress1) // function to print the temperature for a device  (pin 3)
{
  float tempC = sensors1.getTempC(deviceAddress1);
  Serial.print("Temp C: ");
  Serial.print(tempC);
  if (myFile)
  {
    Serial.println(F("\nWriting to test.txt..."));
    myFile.print(F("C: "));
    myFile.print(tempC);
    print_time(); // Call print_time() function to print time on file
    myFile.print(F("\n"));
    Serial.print(F("Done!"));
  }
  else Serial.print(F("Error opening file 1"));
  Serial.println("\n");
}



void printTemperature2(DeviceAddress deviceAddress2) // function to print the temperature for a device (pin 2)
{
  float tempC = sensors2.getTempC(deviceAddress2);
  Serial.print(F("Temp C: "));
  Serial.print(tempC);
  if (myFile)
  {
    Serial.print(F("\nWriting to test.txt..."));
    myFile.print(F("C: "));
    myFile.print(tempC);
    print_time(); // Call print_time() function to print time on file
    myFile.print(F("\n"));
    Serial.print(F("Done!"));
  } else Serial.print(F("Error opening file 2"));

  Serial.println("\n");
}

void print_time() { // print time function

  DateTime now = rtc.now();
  Serial.print(now.year(), DEC);
  Serial.print('/');
  Serial.print(now.month(), DEC);
  Serial.print('/');
  Serial.print(now.day(), DEC);
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.println();
  myFile.print(now.year(), DEC);
  myFile.print('/');
  myFile.print(now.month(), DEC);
  myFile.print('/');
  myFile.print(now.day(), DEC);
  myFile.print(now.hour(), DEC);
  myFile.print(':');
  myFile.print(now.minute(), DEC);
  myFile.print(':');
  myFile.print(now.second(), DEC);
  myFile.println();
}

我想我以前在另一个网站上见过这段代码。 根据您的代码,我假设您正在制作一个温度记录器并且您想将数据记录在 SD 卡上。 如果您使用 DS1307RTC.h 库和 Time.h 库,那么您的大部分代码都是多余的。 DS1307RTC 是一个通用的 RTC 库。有了它,您就不需要 OneWire、Wire 或 SPI and Wire。 Software Serial 也没有必要。 但是,我建议您在 Github 上查看我的 Arduino DataLogger 库:https://github.com/FreelanceJavaDev/DataLogger

我几乎用尽了我在 Uno 上保存的内存,减少到 22,680 字节 (70%) 的程序存储 space 和 1,237 字节 (60%) 的 SRAM(动态内存)。它会自动配置 RTC 和 SD 卡。它制作一个 CSV 文件导出到 excel 每个月 运行 按日期组织。

我那段时间搭建了几个Arduino数据记录器,所有使用SD卡的都因为内存不足而失败。 SD 本身使用了 ATMega 328 可用内存的一半。再加上几个用于其他硬件的库,您的草图就没有剩余内存了。

我转到24LC512s了。通常一个就足够了,但如果需要,您最多可以使用 4 个不同的地址。这是一个相对较小的内存量,但我发现它总是足够的。生成大到无法分析的兆字节数据太容易了。一个 24LC512 可以容纳一个电子表格所需的数据。唯一的缺点是您必须使用 Arduino 通过 USB 回读数据。

我用前两个字节存储记录数,下一个字节存储每条记录的字节数。 (尽管回想起来后者并不是真正必要的。)您可能认为前两个字节会“耗尽”,因为每次创建新条目时它们都会被重写,但我还没有发生过这种情况。我已经连续使用相同的 Arduino 运行 7 年了(除了草图的一些更新之外没有中断),并且每月生成超过 1000 条记录,所以前两个字节必须更新了那么多次。我从来没有遇到过前两个字节的问题,即使我遇到过,也足够便宜来替换 24LC512。

您甚至可以通过“热插拔”摆脱困境:我离开记录器 运行 并更换新的存储芯片,这样我就可以在不中断记录器的情况下读取数据。 (读取记录数,然后增加它并写入新的数字和数据。)

我为谷仓中的系统做的不仅仅是这些 - SD 卡、RTC、LCD 显示器、GPRS 调制解调器、与其他设备的无线电通信、根据季节的编程定时控制泵、雨量传感器、浮动传感器、温度传感器、电压传感器等。以下是我发现的一些东西:

  • 所有字符串文字都应替换为 F() 宏调用和/或在 Flash 上运行的本机字符串函数。所以,strcpy_P(string1, PSTR(string2)), strcat_P(string1, PSTR(string2)) 这样的调用。
  • 将对设备的大量访问转换为包含这些调用所需的任何数据结构的函数调用。您的 arduino 将更加努力地添加和删除堆栈和堆栈帧,但是当函数完成时,数据结构将从堆栈中删除,并且 Arduino 上的机器周期比 RAM 便宜得多。因此,将您的温度读取代码与您的文件写入分开在一个单独的函数中。 Return 浮点数,然后将该浮点数发送到您的 SD 写入函数。
  • 改为在 debug_print 代码中隐藏所有序列号。因此,您将在调试时使用 debug_print 次调用,它们都会从您的生产代码中完全消失。
  • 确保您只在子程序中调用 SD 代码,并根据需要在这些子程序中实例化实际的 FAT 代码,而不是作为一个大的全局代码。
  • 有许多不同的 SD 库,有些比其他库便宜(在内存方面)。货比三家。
  • 在调用其中一个需要 RAM 的 SD 调用之前,使用其中一种 freeMemory 变体来确定您是否有可用内存。如果你当时没有足够的 RAM,你可能想在 EEPROM 中实现某种循环缓冲区来存储要在有可用 RAM 时写入 SD 的消息。
  • 尽可能使用布尔值和字节而不是整数,并考虑对标志使用位域以节省更多 RAM。您真的要在温度总线上安装多达 32,000 台设备吗?一个字节可以得到255