Как уменьшить размер скетча?

Похоже, я переборщил с хранением Adafrui Trinket:

Sketch uses 5,600 bytes (105%) of program storage space. Maximum is 5,310 bytes.
Global variables use 109 bytes of dynamic memory.

Какие-нибудь советы по сокращению объема моего кода?

#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <RTClib.h>

#define LEDPIN 1
#define TONE 4
#define BTNPIN 3

RTC_DS1307 rtc;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(4, LEDPIN, NEO_RGB);

long fadeTime = 1 * 10000L; // X минут
int colorStops = 256;
int delaySpeed = fadeTime / colorStops;
int notes[] = {262,294,330,349};
int timeHour = 14;
int timeMinute = 43;
uint32_t alarmLength = 5 * 60000L; // 5 минут

void setup() {
  rtc.begin();
  if(!rtc.isrunning()) {
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }

  pinMode(TONE,OUTPUT);
  pinMode(BTNPIN, INPUT);
  digitalWrite(BTNPIN, HIGH);

  strip.begin();
  strip.show();
}

void loop() {
  DateTime now = rtc.now(); 

  if (now.hour() == timeHour && now.minute() == timeMinute && now.second() == 0){
    // Fade in light
    for(int i = 1; i<colorStops; i++){
      if (kill() == true){ break; }

      for(int np = 0;np<strip.numPixels(); np++){
        strip.setPixelColor(np, strip.Color(i,i,0));
      }
      strip.show();
      delay(delaySpeed);
    }

    // Играть тон после света полностью яркий
    for( uint32_t tStart = millis();  (millis()-tStart) < alarmLength; ){
      if (kill() == true){ break; }

      for(int np = 0;np<strip.numPixels(); np++){
        strip.setPixelColor(np, strip.Color(random(100,255),random(100,255),random(100,255)));
      }
      strip.show();

      beep(TONE, notes[random(0,3)], 50);
      delay(100);
    }

    for(int np = 0;np<strip.numPixels(); np++){
      strip.setPixelColor(np, strip.Color(0,0,0));
    }
    strip.show();
  }
}

void beep (unsigned char speakerPin, int frequencyInHertz, long timeInMilliseconds)
{   // http://web.media.mit.edu/~leah/LilyPad/07_sound_code.html
  int x;   
  long delayAmount = (long)(1000000/frequencyInHertz);
  long loopTime = (long)((timeInMilliseconds*1000)/(delayAmount*2));
  for (x=0;x<loopTime;x++)   
  {  
    digitalWrite(speakerPin,HIGH);
    delayMicroseconds(delayAmount);
    digitalWrite(speakerPin,LOW);
    delayMicroseconds(delayAmount);
  }  
}

bool kill() {
  if (! digitalRead(BTNPIN)) {
    for(int np = 0;np<strip.numPixels(); np++){
      strip.setPixelColor(np, strip.Color(0,0,0));
    }
    strip.show();
    return true;
  }
  else {
    return false;
  }
}

, 👍6

Обсуждение

Одним из предложений было бы изменить любые переменные int, которые, как вы знаете, не будут выходить за пределы диапазона [-127, 127] , на переменную byte. тип данных byte имеет размер 1 байт, int-4 байта. Это аналогичное понятие может быть использовано и для других переменных., @Jonathan

Вы пробовали разные уровни оптимизации компилятора?, @sekdiy

@JonathanSmit: Диапазон byte равен [0, 255], а не [-127, 127], поскольку он типизирован как uint8_t. И " int` - это 2 байта, а не 4., @Edgar Bonet

Опс, прости за это, совершенно не приходило мне в голову. Я забыл, что байт без знака. Кроме того, разве ints не 4 байта на большинстве 32-битных и 64-битных компиляторов? http://stackoverflow.com/questions/11438794/is-the-size-of-c-int-2-bytes-or-4-bytes, @Jonathan

@JonathanSmit: “Кроме того, разве ints не 4 байта на большинстве 32 - битных и 64-битных компиляторов?_” Да, они есть, но ATtiny85 внутри брелка-это 8-битный чип., @Edgar Bonet

Я считаю, что избавление от arduino digitalRead/Write() и pinMode может сэкономить место. В качестве быстрого теста я экономлю 6% места в программе, удаляя один digitalRead и pinMode. Это дает вам идею., @RSM

Мне интересно, есть ли более компактный способ выполнить намерение, стоящее за функцией "звуковой сигнал". В конечном счете я пытаюсь просто заставить пьезоэлектрик генерировать набор быстрых звуковых сигналов (эта штука предназначена для сигнализации)., @Shpigford

`Мне интересно, есть ли более компактный способ выполнить намерение, стоящее за функцией звукового сигнала " - однако для Atmega328 я написал небольшую библиотеку. Вы должны быть в состоянии адаптировать его. <http://www.gammon.com.au/forum/?id=11504&reply=11#reply11>, @Nick Gammon


3 ответа


Лучший ответ:

4

AdaFruit Trinket просто не имеет много памяти - 8 Кб, из которых 3 Кб используется загрузчиком.

Порядок, в котором я ищу вещи, чтобы уменьшить размер следа программы Arduino, таков:

  • данные (например, большие строки)
  • библиотеки
  • ваш код (особенно делать одно и то же несколько раз, которые могут быть объединены в циклы или функции).

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

Кажется, что вы используете часы только для определения года - это похоже на большие накладные расходы только для того, чтобы выяснить, какой это год. Затем он ничего не делает в течение последующих лет (или предыдущих лет), если я правильно прочитал ваш код. Может быть, вы подумаете о том, чтобы удалить этот код? Тогда вы можете пропустить всю библиотеку RTC. По крайней мере, попробуйте посмотреть, сколько вы сэкономите. Я подозреваю, что библиотека RTC будет использовать другую библиотеку для выполнения основной работы.

Вы используете библиотеку <Wire.h>, но не ссылаетесь на нее в своем коде. Он должен быть оптимизирован (если не используется в другом месте), но нет никаких причин иметь его там.

Вы также можете сэкономить немного места, заменив инициализированные переменные, которые не меняются на константы. Хранение 25 в переменной занимает 1 байт, плюс 2 байта каждый раз, когда вы ссылаетесь на нее; 25 в константе оптимизируется и будет занимать 1 байт каждый раз, когда вы ссылаетесь на нее.

Как уже упоминалось в комментариях, вы также можете сохранить несколько байтов, указав размер ваших int, особенно инициализированных.

Другой вариант - использовать более крупный чип - ATmega328P, используемый в (например) Arduino, имеет 32 Кб флэш-памяти-в 6 раз больше (доступного) размера.

Вы также можете сохранить некоторые байты , удалив предложение if(!rtc.isrunning ()) {, как только вы настроили его один раз.

Дополнительно: у меня был некоторый успех в уменьшении следов путем переопределения подмножества библиотеки. Обычно библиотеки очень хорошо реализованы, но если ваши потребности существенно отличаются/проще, чем то, для чего была разработана библиотека, или вы готовы отказаться от ее частей (например, проверки ошибок), то ИНОГДА это может помочь. ПРЕДУПРЕЖДЕНИЕ! За это приходится платить ВЫСОКУЮ цену (кодирование, отладка, потеря проверки ошибок, необнаруженные ошибки и т. Д.), и в конце концов это может вас ни на чем не спасти.

Вы также можете (иногда) перемещать данные (например, большие строки) в eeprom. Брелок имеет 512 байт eeprom. Возможно, вам понадобится один скетч для загрузки данных в eeprom, который будет запущен перед загрузкой основного скетча. eeprom имеет свои собственные накладные расходы, поэтому может не стоить 512 байт, он медленнее (что, вероятно, не будет иметь значения для этого), и у вас есть ограниченное количество записей.

Еще несколько байтов можно сохранить, предварительно выполнив все вычисления для частоты/времени тона и пожертвовав чем-то. Например, вы подаете звуковой сигнал в течение 50 мс. Если, вместо того чтобы передавать частоту (от 262 до 349 Герц) и вместо того, чтобы предварительно рассчитать delayAmount (по 262 Гц, это будет 1,000,000/262 = 3817ms); ваши looptime за это ((timeInMilliseconds1000)/(delayAmount2)) = (501000)/(38172) = 6.55 циклов. 7 циклов, вероятно, "достаточно близко" для всех тонов, которые вы хотите произвести. Таким образом, вы можете жестко закодировать 7 циклов (используя константу 7 для looptime) и избавиться от этой формулы для расчета looptime.

Аналогичным образом, если вы не заботитесь слишком много о точной частотой (которая варьируется между 3817ms и 2865ms в зависимости от вашего случайных чисел), можно указать delayamount как 2865+случайный(0,3)*238 (убедитесь, что вы храните как минимум случайных чисел, вы не хотите, чтобы он менялся в курсе!).

Теперь у вас есть:

#define random_beep_length 7
void random_beep (unsigned char speakerPin)
{   // http://web.media.mit.edu/~leah/LilyPad/07_sound_code.html
    // Сильно модифицированный!
  int x;
  unsigned char pitch = 2865+random(0,3)*238;
  for (x=0;x<random_beep_length;x++)   
  {  
    digitalWrite(speakerPin,HIGH);
    delayMicroseconds(delayAmount);
    digitalWrite(speakerPin,LOW);
    delayMicroseconds(delayAmount);
  }  
}
,

Спасибо за советы! Я забыл обновить свой код, когда делал отладку. На самом деле мне нужен RTC для определения текущего часа и минуты (что теперь отражено в моем посте)., @Shpigford

Я думаю, что это отличная идея - установить часы один раз - в отдельном скетче - а затем опустить ту часть, которая проверяет, работают ли часы. Если это не поможет, измените библиотеку часов (или сделайте копию), чтобы исключить части, устанавливающие время., @Nick Gammon

“ _ вы можете указать delayamount как 2865+random(0,3)*238". Или, еще лучше, ' 2945+random(0,4)*256. Это экономит 8 байт за счет небольшой неточности в частотах. Или эквивалентная, но менее читаемая форма: 2945+(random()&3)*256` (экономит 18 байт)., @Edgar Bonet

@EdgarBonet, тогда почему бы не (11+random()&3)*256, сохранив еще один байт? :), @AMADANON Inc.

Сумма этих чаевых сделала свое дело. Сейчас я нахожусь на уровне 97%. :), @Shpigford

Вы, вероятно, имеете в виду beep((11+(random()&3))*256);. Когда я компилирую его, он занимает 28 байт flash, v. s. 20 байт для beep(2945+(random()&3)*256);. По какой-то причине gcc-Os вычисляет вашу сумму как 32-битное число. Это можно исправить с помощью beep((11+((uint8_t)random()&3))*256);, который выглядит уродливо, но компилируется только до 16 байт! :-), @Edgar Bonet


3

Вариантом, еще не упомянутым, но, возможно, стоит упомянуть, было бы удалить загрузчик и запрограммировать чип непосредственно с помощью аппаратного программатора (или другого Arduino). Это увеличит доступную память для скетчей с 5 КБ до полных 8 Кб на чипе.

,

Это хорошая идея, однако имейте в виду, что вам нужно будет вернуть загрузчик, если вы когда-нибудь захотите использовать его так, как он был изначально разработан., @Nick Gammon

Есть ли где-нибудь какое-нибудь руководство по этому поводу? У меня есть Uno, с которым, я полагаю, я мог бы это сделать., @Shpigford


3

Как уже упоминал AMADANON Inc. в своем ответе, вы должны квалифицировать как постоянные все постоянные переменные в начале программы. Это, вероятно, самая большая экономия места.

Вы можете получить что-то вроде 100 байт, используя прямой доступ к порту вместо pin/digitalRead/digitalWrite. В случае ATtiny85 у вас есть только порт B, а номера выводов Arduino совпадают с номерами битов Atmel. Поэтому вы бы, например, использовали PINB & _BV(BTNPIN) вместо digitalRead(BTNPIN).

В beep () есть несколько возможностей оптимизации:

  • передайте только один параметр, так как громкоговоритель и время в миллисекундах являются константами в вашей программе
  • пусть этот параметр имеет значение delayAmount вместо frequencyinherz, и сохраните эти задержки вместо частот в массиве констант notes [].
  • не вычисляйте количество итераций (время цикла с неправильным названием) заранее вместо этого вычислите затраченное время и цикл, пока вы не достигнете требуемого времени

Вот моя версия beep() с этими оптимизациями:

void beep(int delayAmount)
{
  for (uint16_t t = 0; t < BEEP_TIME*1000/2; t += delayAmount)
  {  
    PORTB |= _BV(TONE);  // digitalWrite(TONE,HIGH);
    delayMicroseconds(delayAmount);
    PORTB &= ~_BV(TONE);  // digitalWrite(TONE,LOW);
    delayMicroseconds(delayAmount);
  }  
}

Обратите внимание, что переменная цикла фактически составляет половину прошедшего времени. Это экономит несколько байтов. Делая количество итераций постоянным, как предлагает AMADANON Inc., также экономится несколько байтов (6 байт в моем тесте).

Вы также должны удалить все вызовы функции strip.Color(): замените каждый экземпляр strip.setPixelColor(np, strip.Color(r, g, b)) на strip.setPixelColor(np, r, g, b).

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

,

"постоянные переменные"? лол! Я знаю, что ты имеешь в виду :), @AMADANON Inc.