Оптимизация кода для использования меньшего количества флэш-памяти и SRAM

Я пытаюсь уменьшить размер скетча, и мне удалось уменьшить как SRAM, так и флэш-память.

From:
1570 SRAM - 32144 Flash memory
To:
644  SRAM - 19458 Flash memory

Вот что я сделал до сих пор:

Включение LTO

Использование PROGMEM

Удаление string и использование char

Удаление загрузчика

Использование манипулирования портами

Использование регистров для объявления выводов/входов

Удаление/Оптимизация/Объединение; ненужные/повторяющиеся коды в скетче

Использование EEPROM мне не подходит.

Что еще я могу сделать, чтобы продвинуть оптимизацию на один или два шага вперед?

, 👍2

Обсуждение

ArduinoIDE спроектирован так, чтобы быть простым и не быстрым. Чтобы действительно оптимизировать свой код, переключитесь на AtmelStudio и напишите логику на уровне реестра. ArduinoIDE загружает множество библиотек из коробки. Некоторые из них, вероятно, не нужны в вашем эскизе., @Filip Franik

Измените уровни оптимизации компилятора. Похоже, вы можете [сделать это в коде](https://ucexperiment.wordpress.com/2017/05/20/modify-arduino-optimization-levels-on-the-fly/), хотя я сделал это путем изменения файла boards.txt в прошлом., @Gerben

Также используйте правильный тип для (глобальных) переменных. Используйте byte вместо int для переменных, значения которых всегда находятся в диапазоне от 0 до 255., @Gerben


2 ответа


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

5

Что вы можете сделать в дополнение к упомянутым вами элементам:

Уменьшение SRAM

  • Используйте наименьшее количество типов данных для целых чисел, например, используйте 8-битный тип данных вместо целого числа по умолчанию, это сэкономит вам один байт для каждой переменной. См. примечание ниже о типах. Я создал свои собственные типы (если их еще не было) для int32_t, uint32_t, int16_t, uint16_t, int8_t и uint8_t и всегда использовал наименьший из возможных.
  • Используйте без знака вместо знака для типов, которые не могут быть отрицательными. Это может сэкономить вам один байт, если вы можете предотвратить использование байта без знака вместо целого числа со знаком, поскольку значение может находиться в диапазоне [128..255]. То же самое для unsigned int вместо signed long.
  • Используйте битовые поля, если вы используете несколько логических значений внутри структуры или класса. Убедитесь, что битовые поля объявлены последовательно.
  • Упаковать данные в интеллектуальные структуры... То есть, если у вас есть две переменные, где одна может быть 0..7, а другая также может быть 0..7, сохраните их вместе, используя битовые поля (в сочетании с логическим значением битового поля выше).
  • Предотвращает плавание/удвоение (см. комментарий KIIV ниже), вместо этого:
  • Вместо использования числа с плавающей запятой умножьте значение на требуемую точность и сохраните его в виде целого числа (если вам не нужна высокая точность и оно соответствует целочисленному типу).
  • Если вам нужно сохранить много строк, и в этих строках нет символов выше 128, вы можете сохранить 1 бит на байт, конечно, это потребует некоторой работы по программированию для обработки 7-битных символов «ASCII».
  • То же самое, если вам нужно хранить много значений, которые могут быть, например, от 0 до 50, вы можете использовать 6 бит вместо 8 бит; вы не можете использовать битовые поля, но вы можете создать интеллектуальный массив и запрограммировать свои собственные функции get/set для доступа к 6-битным значениям.
  • Если вы используете библиотеки, которые объявляют большие буферы, постарайтесь минимизировать эти буферы, это может привести к их копированию и изменению вручную.
  • Не делайте свои собственные буферы глобальными, вместо этого временно используйте их как локальные переменные, когда они вам понадобятся. Если у вас есть два таких буфера, вы не выделяете оба буфера одновременно; однако обратите внимание, что во время выполнения этому буферу по-прежнему требуется место (поэтому вам необходимо рассчитать или сделать предположение/оценку максимального размера стека).

Подавление вспышки

  • Не используйте повторяющиеся блоки кода, а создавайте функции с параметрами, различающимися между различиями дублированного кода.
  • Вместо справочных таблиц создайте функцию, которая ее вычисляет (если возможно). Конечно, выигрыш зависит от длины таблицы поиска и длины функции.
  • Вместо строк отладки используйте числа.

SRAM/Flash Reduction

  • Разделите ваше приложение на несколько микроконтроллеров, конечно, это добавит какой-то протокол связи (UART, I2C, SPI).
,

Это больше похоже на следующее: не используйте float/double на архитектурах без аппаратного FPU (почти только некоторые платы Cortex M3/M4 имеют его). И, например, Arduino на базе AVR — это просто псевдоним для числа с плавающей запятой., @KIIV

@KIIV Спасибо за комментарий, не знал, что псевдонимы одинаковые.k, @Michel Keijzers

Вы также можете перейти на процессор с большим объемом SRAM, например, 16 КБ в Atmega1284P. Я использую этот чип во многих проектах на основе Arduino. Два аппаратных последовательных порта, флэш-память 128 КБ, оперативная память 16 КБ и 32 ввода-вывода., @CrossRoads

@CrossRoads Я не принимал во внимание аппаратные решения (из-за того, что OP не хочет EEPROM, что более или менее равносильно использованию другого MCU). Жаль, что они не делают (дешевый) Arduino с этим микроконтроллером, я думаю, что 2 КБ SRAM действительно являются пределом для многих проектов. Сейчас я делаю свой с Mega и использую внешнюю EEPROM для хранения. Я думал о внешней SPI RAM, но стараюсь делать свой проект без нее., @Michel Keijzers

Да, Китай не предлагает платы на базе 1284P, как для Uno, Nano и Promini. Я продаю платы, но детали покупаю у Digikey & Mouser, так что там есть некоторая наценка, но это гарантирует, что я получу детали из надежного поставщика., @CrossRoads

@CrossRoads Думаю, так лучше, но мне подойдет Mega. Я хотел бы сделать весь проект с STM32, но я обнаружил, что моих знаний не хватает для быстрого прогресса, поэтому я придерживаюсь простых Arduino, пока не буду готов использовать несколько STM32., @Michel Keijzers


0

Я вспоминаю свои первые годы работы программистом на C в 80-х.

Несколько мыслей в дополнение ко всем уже данным отличным советам...

  • В зависимости от того, сколько данных использует ваше приложение, вы можете чтение/запись данных на SD-карту (или другое внешнее хранилище). Пока есть определенное количество накладные расходы на библиотеки, выигрыш в том, что у вас будет практически неограниченное хранилище. Это эквивалент того, как мы хранили данные на 5 1/4-дюймовых дискетах еще в старые времена.

  • По возможности избегайте статических переменных.

  • Держите область действия переменной как можно меньше.

  • Исходя из философии, вы должны рассматривать unsigned char как тип данных по умолчанию и «обновляться» до большей занимаемой площади только тогда, когда требуется код.

,

Re «_используйте динамическую память как можно больше_»: вам, вероятно, следует быть более конкретным. Услышав «динамическую память», большинство подумает «malloc()», чего следует избегать, если можете. Я предполагаю, что вы имеете в виду «автоматическое размещение», т.е. локальные переменные, не определяемые как «статические»., @Edgar Bonet

Re «_повторно используйте переменные, если они вам не нужны одновременно_»: −1, так как это **ужасный совет**! Пожалуйста, воздержитесь от поощрения такой ужасно плохой практики. Это могло быть разумной оптимизацией 30 лет назад, но, по крайней мере, 20 лет назад стало довольно сложно найти достаточно тупой компилятор, чтобы не делать эту оптимизацию самостоятельно. Доверяйте своему компилятору и пишите понятный код. Или откажитесь от компилятора, если вам удалось найти _этот_ плохой., @Edgar Bonet

Забавно, что поддержание минимально возможной области видимости является взаимоисключающим при повторном использовании переменных. Повторное использование переменных также подвержено ошибкам, но, возможно, немного менее подвержено ошибкам, чем объявление переменных и инициализация позже (часто позже, чем они используются), @KIIV

Согласен с отзывами, обновил свой ответ, чтобы отразить это., @Rob Sweet