Подстрока: Что вызывает усечение вывода?

Подозреваю, что использую слишком много ресурсов из-за строк, но я новичок в Arduino, поэтому не уверен, где я ошибаюсь. Любая помощь будет оценена по достоинству.

Что он должен делать по завершении: "Напоминание о дне рождения"

  • Прочитайте SD-карту для файла "день рождения" ("MM-DD.txt"), что соответствует "сегодня".
  • Файл содержит имя человека и год рождения.
  • Мигание светодиодов и отображение имени и возраста человека на OLED.
  • Устройство не будет отображаться до чьего-либо дня рождения.

Я использую SD-карту с простым форматом, чтобы легко добавлять новые дни рождения.

Output:
⸮Initializing SD card...
card initialized.
25
111
opening: 04-25.txt
2020"Test Person"
bYearStr: 20

Файл SD-карты:

Name: MM-DD.txt 
Example: 04-25.txt 

Contents: Birthyear, Name 
Example: 2020"Test Person"

Скетч

#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "RTClib.h"

RTC_DS3231 rtc;

int age;
int led;
int dly;
String bName;
String bYearStr;
int bYear;
int currentDay;
int  tDy;
int  tMth;
int  tYr;

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(9600);
  delay(3000); // wait for console opening

  // Setup Candle pins
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  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));
  }

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display(); // this command will display all the data which is in buffer
  display.setTextColor(WHITE, BLACK);

  const int SdCardPin = 10;
  Serial.println("Initializing SD card..."); // Verifying SD Card.
  //  if (!SD.begin(SdCardPin)) {
  if (!SD.begin(10)) {
    Serial.println("Card failed, or not present"); // don't do anything more:
    while (1);
  }
  Serial.println("card initialized.");

  currentDay = 111;
}


void loop() {
  age = 0;

  DateTime now = rtc.now();
  tDy = now.day();
  tMth = now.month();
  tYr = now.year();
  Serial.println(tDy);
  Serial.println(currentDay);

  if (currentDay != tDy) {
    String fileName;
    String m = String(tMth);
    String d = String(tDy);

    if (tMth < 10) {
      String mm = m;
      m = "0" + mm;
    }
    if (tDy < 10) {
      String dd = d;
      d =  "0" + dd;
    }
    fileName = m + "-" + d + ".txt";

    //String fileName = "01-01.txt";
    Serial.println("opening: " + fileName);
    File f = SD.open(fileName);
    delay(3000);

    if (!f) {
      Serial.println("open failed");
    } else {
      while (f.available()) {
        String line = f.readStringUntil('\n');
        Serial.println(line);
        bYearStr = line.substring(0, 4);
        bName = line.substring(4);
        delay(1000);
        currentDay = tDy;
        break;
      }
      f.close();
    }

    Serial.print("bYearStr: ");
    Serial.println(bYearStr);
    Serial.print("bName: ");
    Serial.println(bName);
    int bYear = bYearStr.toInt();
    age = tYr - bYear;
    Serial.print("Age: ");
    Serial.println(age);


  } else { // Start current day equal
    display.setTextSize(1);
    display.setCursor(38, 10);
    display.print("Birthday!");
    display.println();

    display.setTextSize(1);
    display.setCursor(0, 30);
    display.print("Name: ");
    display.print(bName);
    display.setCursor(0, 40);
    display.print("Age: ");
    display.print(age );
    display.println();

    led = random(3, 6);
    dly = random(80, 250);
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(dly);              // wait
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  } // End current day

  display.display();

} // End void Loop

, 👍1

Обсуждение

Нет больше последовательного вывода после 2 первых символов строки "2020" ? Очень странно. Вы можете упростить свой скетч только в тестовой среде, без всего, что связано с отображением, в конечном итоге даже без RTC., @DataFiddler

Не используйте класс String. Замените его массивами символов. Вероятно, у вас заканчивается объем оперативной памяти., @SBF

Спасибо. @DataFiddler Я сделал это, и это работает с серийной печатью. Скетч также работает, когда я не использую SD-карту, но помещаю даты рождения в меньший тестовый массив (3 имени). Я сталкиваюсь с проблемами с большими массивами (13 имен). Я разобрал его на части и строил заново. Для информации, я вообще не получаю дисплей, похоже, что скрипт останавливается на выходе., @E_Ja

Спасибо, @SBF, я боялся, что именно это и происходит., @E_Ja


1 ответ


3

Вполне вероятно, что у вас нет памяти. Чтобы узнать, действительно нужно следить за указателем стека и структурой кучи. В вашем коде происходит достаточно, чтобы я не мог разумно настроить идентичный сценарий, чтобы доказать это.

Библиотека отображения

Наибольшее использование SRAM в вашем проекте происходит из библиотеки Adafruit SSD1306. Он выделяет буфер для всего кадра, который составляет 128 * 64 == 8192 бит, или 1024 байта, половину SRAM вашего Nano. Он будет использовать больше, чем для других данных, хотя, вероятно, не намного больше в процентах.

Вы можете смягчить это использование с помощью библиотеки, такой как торговля некоторым использованием SRAM для дополнительного времени выполнения или более низких частот кадров. Один из них-библиотекаU8g2 . Вы можете прочитать о том, как он может использовать половину буфера в некоторой документации к более старой версии библиотеки; концепция переносится в более новую библиотеку.

Библиотека SD

Следующее по величине использование-секторный буфер библиотек SD-карт, который составляет 512 байт.

Таким образом, между двумя из них идет 3/4 вашего SRAM только в двух буферах, не думая ни о чем другом.

Вы можете избежать этого буфера, используя другую библиотеку. Вы можете попробовать PETITS. Библиотека, которую он обертывает, говорит, что он использует что-то вроде 44 байт плюс любой стек вызовов.

Строковые литералы

У вас есть по крайней мере 206 байт строковых литералов только в вашем скетче, предполагая, что компилятор не может определить, что ни один из них на самом деле не используется. В библиотеках могут быть и другие, хотя, вероятно, их немного. Итак, теперь у нас осталось по крайней мере 306 байт или меньше SRAM.

Размер Строка
35 "RTC потерял мощность, давайте установим время!"
28 "Карта провалилась, или нет"
24 "Инициализация SD-карты..."
18 "Не смог найти RTC"
18 - карточка инициализирована.
12 "открыть не удалось"
11 "bYearStr: "
10 "открытие: "
10 -День рождения!
10 "01-01.txt"
8 "bName: "
7 "Имя: "
6 "Возраст: "
5 ".txt"
2 "-"
2 "0"
206 Итого

"0" считается только один раз в приведенном выше, хотя у вас их два. Забавный факт: языки C и C++ позволяют идентичным строковым литералам размещаться или перекрываться. Я не знаю , будет ли avr-gcc объединять "Name:" в "bName:", так как у вас есть большие проблемы, о которых нужно беспокоиться, я просто делаю вид, что это не будет, не глядя.

Смягчение может включать использование макроса F() для перемещения некоторых из этих строковых литералов в PROGMEM (flash). Вы не можете сделать это с каждым строковым литералом, но те, где вы включаете их в строковый(F("мы пытаемся избежать их, верно?")) конструктор или в последовательный (или другие экземпляры потока/печати) s .print(F("Hello World"));

Кишки макроса F() - это еще один так называемый PSTR(), который выполняет фактическую работу по помещению строкового литерала в PROGMEM. F() помечает полученный указатель фиктивным типом, который используется при перегрузке функций в библиотеках Arduino. Единственная причина, по которой я упоминаю об этом, заключается в том, что ниже вы увидите, как я использую PSTR для того же самого, только с функцией, которая принадлежит avr-libc, а не Arduino.

Примечание.при инициализации строкового объекта из обычного строкового литерала стоимость SRAM оплачивается дважды за время существования строкового объекта. Одна копия находится в SRAM для литерала, используемого для вызова строкового конструктора, другая копия находится в динамической памяти, выделенной строковому объекту.

Класс String

Это было указано в комментариях; и это правда, что лучше избегать их. Хотя, я думаю, на самом деле не самая большая проблема в вашем случае.

Я вижу, что вы избежали ненужного добавления их вместе для последовательного вывода и вместо этого используете отдельные вызовы .print (), так что вы впереди игры.

Для вашего имени файла можно использовать локальный массив символов фиксированного размера и snprintf_P (), чтобы собрать ваше имя файла, что-то вроде:

char filename[sizeof "MM-DD.txt"];
snprintf_P(
    filename, sizeof filename,
    PSTR("%02u-%02u.txt"),
    tMth, tDy
);

или если вы хотите избежать втягивания зверя printf в свой скетч, предполагая, что он уже не там, вы можете сделать что-то вроде:

const char filename[] = {
    '0' + tMth / 10,
    '0' + tMth % 10,
    '-',
    '0' + tDy  / 10,
    '0' + tDy  % 10,
    '.',
    't',
    'x',
    't',
    '\0'
};

Последовательный

Какой Serial? Да, я имею в виду, что вы мало что можете с этим поделать, но я поднимаю этот вопрос, потому что последняя консервативная цифра выше была 306 байт, и мы вообще не пытались объяснить, что строка делает в куче. Ну, последовательный порт занимает 157 байт в соответствии с простым тестом здесь, что имеет смысл, потому что на UNO он имеет два кольцевых буфера для передачи и приема по 64 байта каждый, полдюжины указателей для настройки аппаратных регистров для использования, а также головные и хвостовые индексы для этих буферов. Есть также еще 12 байт, которые используются для vtable HardwareSerial.

Так что это сводит вас к консервативно 137 байтам SRAM для всего остального.

Все Остальное

Здесь многое не учтено. Ваши глобалы, глобалы библиотек, другие vtables, строковые данные в куче и стеке. Это только те, что приходят на ум. Я имею в виду только переменные счетчика millis()/micros() и fract занимают 9 байт. Если вы действительно хотите углубиться в детали здесь, помимо просмотра указателя стека и структуры кучи, команда avr-nm, выполняемая в файле .elf вашего скетча с его-D,- Sи --size-sort, полезна для определения использования глобальных переменных и тому подобных вещей.

,

На самом деле вы *могли бы* изменить основные файлы (или, возможно, определение платы), чтобы заставить HardwareSerial использовать меньшие буферы. Так что это не полностью выходит из-под вашего контроля. Не уверен, стоит ли его редактировать; он немного ниже в списке., @timemage