Итак надо как-то разбивать программу на логические куски кода : один кусок делает одно, другой другое.
Главная идея функции , что она принимает какие-то данные , потом что-то делает с этими данными и результат в виде новых или измененных данных возвращает обратно на более верхний уровень.
Общий вид функции такой :
uint32_t myFunction (uint16_t I1 , int * pI2, int &I3 , .... )
{
......
return 123;
}
Возвращать результат можно через через оператор return , но также можно и через входные параметры , но только если они объявлены как указатели или ссылки (*pI2 , &I3).
Примечание : ссылки только если у вас С++, в чистом С только указатели имеются.
Код функции располагается в области неизменяемой памяти, например FLASH у контроллеров.
Но , что делает этот код - он манипулирует изменяемыми данными , которые находятся в оперативной памяти SRAM.
При переходе из кода функции в другую вложенную функцию надо как-то запоминать адрес возврата : для этого использую стековую память (стек).
Это та же оперативная память SRAM, но отделенная от кучи (и чем дальше , тем лучше).
Процесс расходования оперативной памяти на самом деле плохо контролируется, так как есть функции динамического выделения памяти типа malloc . Они могут навыделять столько памяти , что куча и стек встретятся лицом к лицу и получится сбой HardFault (контроллер не может дальше работать).
Если у вас не используется динамическое выделение памяти , то все равно еще остается стек , который может расти за счет рекурсивных (многократно вложенных) вызовов функций. Но это уже скорее ошибки программиста.