π Home | π Embedded Systems | π§° Development Toolbox | π Training Courses | π Documents
In this article, I will address these crucial concerns for you.
π Functions are fundamental building blocks in embedded C programming. They allow you to organize code into reusable modules, making programs easier to read, maintain, and debug.
π In embedded systems, functions are used to implement hardware control, handle interrupts, process data, and manage system resources efficiently.
π Understanding how to define, use and debug a functions is essential for writing reliable and efficient embedded software.
Example of function declaration, defintion and function call:
/**
* @brief Reset counter value
* @param None
* @retval None
*/
void Counter_resetCounter(void);
/**
* @brief Reset counter value
* @param None
* @retval None
*/
void Counter_resetCounter(void)
{
g_counter_u32 = 0U;
}
Counter_resetCounter();
an identifier
(function name) and a compound statement
(function body).main
, which can invoke or terminate other system-defined, library, or user-defined functions.π In this Embedded C Function Demo Project
, I start to count up and check if the counter is overed the threshold. If yes, I reset the counter and turn on on-board LED to indicate the user that counter is overed, otherwise, I keep the on-board LED off to indicate user that the counter is counting up.
π‘ The functions are compiled into binary code and stored in the flash memory.
β How can you confirm this?
Watch Window
during debugging, you can see the exact start address of the function.π For example, with the Counter_countUp
function, the address shown in the Watch Window
is 0x0800027C
. Comparing this to the flash memory range 0x0800 0000
- 0x0801 FFFF
specified for the STM32F103C6 in the STM32F103C6 data sheet and the target controllerβs Read/Only Memory Areas
used for the demo project, you can see that the function address is within the flash memory range.
Similarly, you can double check for other functions.
π First, let discuss about an example that how Counter_getCounterThres
function is called.
0x0800036C F7FFFF8E BL.W 0x0800028C Counter_getCounterThres
0x0800028C
which is the start address of Counter_getCounterThres
function.π After that, the program jumps to the Counter_getCounterThres
function and execute the instructions inside of the function, like reading the value of g_counterThres_u32
variable.
π After executed the instructions, the Counter_getCounterThres
function is terminated by the program and return back with the value of g_counterThres_u32
and store it in l_counterThres_u32
variable.
π‘ The stack in embedded software is a special region of memory used to store temporary data such as function parameters, local variables, and return addresses during program execution. When a function is called, its context (including local variables and the return address) is pushed onto the stack. When the function returns, this context is popped off the stack, restoring the previous state.
π‘ The stack operates in a Last-In, First-Out (LIFO) manner, meaning the last item pushed onto the stack is the first to be removed. The stack is managed by the Stack Pointer
register, which keeps track of the top of the stack.
π In the demo with STM32F103C6 target controller, the stack is located in the SRAM area of the memory. It is designed starting from 0x2000 0000
. You can double check with STM32F103C6 data sheet, Read/Write Memory Areas
, Register View
and Memory View
to have more details.
π‘ The function call stack is a data structure that keeps track of active function calls during program execution. Each time a function is called, a new stack frame is created and pushed onto the stack. This frame contains information such as the functionβs parameters, local variables, and the return address.
π‘ When a function completes, its stack frame is popped off the stack, and control returns to the calling function. This process allows the program to manage nested function calls efficiently.
π― In this demo, I would like you focus in the growing of the stack and function call stack by looking to the Stack Pointer - R13(SP)
in the Registers View
and the Function Call Stack
in the Call Stack + Local
View when calling and terminating the functions. You can set a Break Point
inside of the function to stop the program and analyze the Call Stack
.
π When calling the functions, the program create a context for the function by allocating the memory for its parameters, local variables and return address. The push it on the top of the stack and making the stack is growing up. Stack Pointer
is also updated to track the top of the stack. The called function is pushed in the Call Stack
.
main()
-> Counter_isOvered()
-> Counter_getValue()
[!NOTE] π‘ In the ARM-Cortex, stack growthing is equal with the decreasing of
Stack Pointer
value because the top of the stack is initialized with allowed biggest address of the SRAM memory.
π When terminating the functions, the function context includes parameters, local variables is popped off the stack, restoring the previous state. The stack is growing down, the Stack Pointer
is updated to track the top of the stack and the called function is popped out of the Call Stack
.
Counter_getValue()
-> Counter_isOvered()
-> main()
π Understanding the stack and call stack is essential in embedded software development for several reasons:
By keeping track of stack and call stack usage, you can design safer, more efficient, and more maintainable embedded software.
βοΈ Before writing any function, consider the big picture:
Functions help organize software, making it easier to understand and maintain.
β However, excessive use can lead to problems:
βοΈ Proper stack management is crucial in embedded systems, as stack overflows can lead to unpredictable behavior, crashes, or corruption of program data. The size of the stack is typically limited by the microcontrollerβs memory resources, so functions should be designed to use stack space efficiently.
βοΈ Function calls involve context switching, which can add overhead. To improve execution speed, consider implementing simple functions as inline functions. This reduces call overhead and can optimize performance. (More details on this topic will be provided in a future article.)
β Avoid using recursion unless absolutely necessary, as it can quickly lead to stack overflow due to the rapid increase in function call depth.
βοΈ There are techniques to eliminate recursion, such as using loops or iterative algorithms, but these will be discussed in a future article.
βοΈ Before declaring and defining a function, consider these basic questions:
βοΈ Here I would like to further explain the usage scope of a function, also known as the functionβs storage class.
π‘ A local function is intended for use only within the source file or module where it is defined and cannot be called from outside. The static
keyword is added before the function declaration and definition to specify this scope.
Example:
/**
* @brief Count up counter
* @param None
* @retval None
*/
static void Counter_countUp(void)
{
g_counter_u32++;
}
### Global Function
π‘ A global function, on the other hand, can be used both inside and outside the source file or module. It does not require any prefix.
Example:
/**
* @brief Reset counter value
* @param None
* @retval None
*/
void Counter_resetCounter(void);
π My Repositories
If you have any thing would like to discuss or cooperate with me, please donβt hesitate to contact me via:
π Home