С появлением первого реально работающего проекта на Atollic True Studio и STM32F205VG (с FreeRTOS) захотелось поделится общими соображениями логики разбиения программы на кирпичики.
Исходим из того , что есть память SRAM для изменяемых данных и память FLASH (постоянная неизменяемая). Но и первой и второй памяти всегда не хватает. Понять куда компилятор поместил переменную очень легко : 0х8xxxxx - FLASH , 0x2xxxxxx - SRAM . Это сразу очень многое проясняет.
Когда не хватает SRAM - проявляется это в вылете в HardWareFault при соединении памяти стека с кучей (это все в SRAM).
Когда не хватает FLASH - компилятор еще на этапе сборки обычно ругается (см. настройки ld файла).
Используем глобальные массивы
Во-первых стараемся НЕ плодить много массивов для хранения данных (приемных буферов UART , SPI , I2C и т.д.). Ибо массив на uint8_t arr[1000] это 1000 байт оперативной памяти SRAM.
Во-вторых выносим эти массивы из функций и делаем глобальными, доступными из разных модулей (файлов проекта) через extern.
В третьих передаем в функции через параметры указатели на эти массивы , хотя можно обращаться к ним можно и как к глобальным данным (т.е. доступным отовсюду).
void myFunction1 (uin8_t *arr)
{
..
myFunction2 (arr)
..
}
Поскольку у нас используются несколько потоков :
Поток для обработки команд нажатия клавиш с клавиатуры
Поток для посылки команды WiFi модулю ESP8266 и получения данных обратно по UART
Поток для печати данных на термопринтере
Поток для работы с GSM модулеи по UART
Поток для вывода данных на LCD дисплей по I2C
У нас соответственно для каждого потока по-любому будут использоваться буферы для приема и посылки данных.
И использовать один буфер для разных потоков логически ну никак не получается.
Поэтому придется смирится с тем , что у нас будет несколько довольно больших буферов (обычно это буферы приема данных).
Выделяем периферию , которой мы управляем однонаправленно
"Однонаправленно" имеется ввиду , шлем команды в периферийное устройство и не паримся - когда команда выполнится . То есть не ждем ответа от периферии.
Выделяем теперь "тормозную" периферию
Подумаем логически : допустим мы печатаем на термо-принтере (мы управляем процессом прередачи байтов). Принтер : устройство вообще говоря с точки зрения контроллера ну очень "тормозное".
Используем дополнительно внешнюю память
То есть данные большого объема можно, получая извне частями, выгружать сначала на внешнюю Flash память типв AT45 , MX25 и потом частями ее обрабатывать.
От этого варианта похоже никуда не деться.
Поэтому подготовить весь массив точек (питчей) сначала во внешней Flash памяти , а потом выводить порциями по 512 байт допустим визуально по скорости будет и незаметно совсем.
То же можно сказать и о жк дисплее.
Выделяем периферию , с которой мы ожидаем поступление данных
То есть мы не знаем когда придут данные с периферии (например ESP 8266 присылает ответ на команду по UART).
Принимать данные надо "оперативно" , т.е. быстро. И тут без оперативной памяти не обойтись.
Но вопрос в том как быстро и в каком объеме данные приходят. Допустим приемный буфер на 1000 байт. Делаем другой приемный буфер еще на 1000 байт.
И пока прием идет в один из буферов, из другого качаем данные в другом потоке в другую , например, внешнюю память.
Конечно надо соизмерять скорость прихода данных и выгрузки в хранилище. Если скорость прихода быстрее , то ничего не получится. Либо надо использовать много потоков выгрузки (в общем не оченб это хорошо).
Просто фишка в том , что данные по UART у нас приходят по прерыванию , надо быстро принять (т.е. поместить байт в приемный буфер) и быстро выйти из продедуры прерывания для приема следующего байта, иначе следующий байт пропадет.
Можно на время таких событий попробовать подвесить остальные потоки. Ну конечно поставить контроль начала и конец буферов, чтобы первыми узнать , что мы не справляемся с приемом данных. Тогда можно просто повторить все заново.
Экономим оперативную память
Все постоянные данные (числа, строки) помечаем const
Естественно только те значения, которые будут постоянны. И они попадут во Flash память (неизменяемую).
const uint8_t * str="blablabla";
В структурах строки тоже делаем const.
Например есть структура :
typedef struct
{
const uint8_t * name;
size_t size;
}MENU_ITEM;
Объявляем глобально массив структур (допустим это древовидное меню будет) :
MENU_ITEM menu[10];
Инициализируем далее данные в любой функции :
menu[0].name = "menu1";
menu[0].size = 5;