общие принципы логической организации программы

С появлением первого реально работающего проекта на 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;