Ai Ho

Jet Station πŸš€

Menu

🏠 Home | πŸš€ Embedded Systems | 🧰 Development Toolbox | πŸŽ“ Training Courses | πŸ“š Documents

Embedded C Macro Demo Project

🎯 As you may already know, macros in C are commonly used to replace constant numbers during preprocessing. In this article, I will recap the basic usage of macros and dive deeper into their applications in embedded development to answer the questions below:

Embedded C Macro Demo Project

Basics about Macros

❓ What is a macro in C?

❓ Which compilation step are macros used in?

Demo Project

Software Design

πŸ’‘ In this demonstration, I simulate a system tick that counts up 1000 times per second. When 500ms have elapsed, the software sends a notification to the user by turning on the on-board LEDs on the development board.

πŸ’‘ I would like to make the high-level software module compatible with 2 types of boards: the STM32F103 Blue Pill Board and the Tiva C Launchpad EK-TM4C123GXL with the help of Board Support Package (BSP).

Software Design

There are some goals I would like to achieve:

Implementation Approach Without Macro

πŸ”½ Source: Demonstration Without Macro

In the first implementation approach, I use constants, variables and functions to implement the software without using any macros. I have the software running as I expect but it has failed to achieve the goals.

Problems

Some problems you can figure out when first looking at the implementation:

Problems

Always Compiled Code Problem

Practice With Macro To Enhance The Robustness Of The Software

πŸ”½ You can find the final source here: Demonstration With Macro

πŸ”¨ Development Boards

πŸ”§ Development Tools

1. Constant Definition

πŸ‘‰ Here I see an opportunity to use macros to replace meaningless constants such as 4294967295U, 0U, 1000U.

/* Macro for maximum value of ticks */
#define TICKS_MAX_VALUE (4294967295U)

/* Macro for reset value of ticks */
#define TICKS_RESET_VALUE (0U)

/* Macro for ticks per second */
#define TICKS_PER_SECOND (1000U)

Constant Definition Example

Benefits of using macros:

2. Simple Expression

❔ If the code doesn’t have any comments, you may not know what the exact purpose of this line of code is:

uint32_t interval = (500U) * (1000U) / (1000U);

πŸ‘‰ I replace it with meaningful code using macros, which makes it easy to understand:

/* Macro to convert milliseconds to ticks */
#define MS_TO_TICKS(ms) ((ms) * TICKS_PER_SECOND / 1000U)

uint32_t interval = MS_TO_TICKS(500U);

Simple Expression with Macros

Benefits of using macros for expressions:

3. Function-like Macro

πŸ‘‰ The function Ticks_elapsed() is quite simple, so I replace it with a function-like macro. There is no function call overhead because the code from the macro is inserted directly for faster execution.

Function-like Macro Example

Benefits of using function-like macros:

4. Conditional Compilation & Portability

πŸ‘‰ I can use macros for conditional compilation with #ifdef or #if defined to include/exclude features from the program. The unused code is completely removed from the program.

#if defined (BOARD_STM32F103C6_BLUEPILL)
	#include "bsp_stm32f103_bluepill.h"
#elif defined (BOARD_EK_TM4C123GXL)
	#include "bsp_ektm4c123gxl.h"
#else
	#error "Development Board is not specified"
#endif

Benefits of using macros for conditional compilation:

5. Header Guards

❌ Multiple inclusion happens when the file is included more than one time. The variable, constant, data type is redefined causing this error.

Header Guards Problem

πŸ‘‰ To prevent the multiple inclusion error and help the compilation faster, we can also use the header guard technique.

βœ”οΈ Example: Before including, the complier will check if bsp_stm32f103_bluepill.h file is already included or not by __BSP_STM32F103_BLUEPILL_H__ macro. If yes, the complier will skip processing this file content.

#ifndef __BSP_STM32F103_BLUEPILL_H__
#define __BSP_STM32F103_BLUEPILL_H__

typedef enum
{
	BAD = 0U,
	GOOD
} BoardStatus;

void BSP_Stm32f103BluePill_turnOnBoardLedsOn(void);

#endif

Header Guards Solution

Benefits of using macros for header guards:

6. Stringizing and Token Pasting

πŸ‘‰ You can use the macro to manipulate the string as below:

#define TO_STRING(x) #x
#define CONCAT(a, b) a##b

Benefits of stringizing and token pasting macros:

Best Practices for Using Macros

βœ”οΈ Here is a summary of some best practices you should follow when using macros.

1. Always Use Parentheses to Avoid Unexpected Behaviour

/* Bad */
#define SQUARE(x) x * x

/* Good */
#define SQUARE(x) ((x) * (x))

2. Use UPPERCASE for Macro Names

#define MAX_BUFFER_SIZE 1024U
#define LED_PORT GPIOC

3. Add β€˜U’ Suffix for Unsigned Constants

#define TIMEOUT_MS 5000U
#define MAX_COUNT 100U

4. Use Include Guards to Avoid Mutiply Inclusion

#ifndef MY_HEADER_H
#define MY_HEADER_H
/* content here */
#endif

5. Avoid Side Effects in Function-like Macros

/* Bad - evaluates 'x' multiple times */
#define MAX(x, y) ((x) > (y) ? (x) : (y))

/* Better - use inline functions for complex logic */
static inline int max(int x, int y) {
    return (x > y) ? x : y;
}

6. Use Meaningful Names

/* Bad */
#define N 10

/* Good */
#define MAX_USERS 10

7. Document Complex Macros

/**
 * @brief Convert milliseconds to system ticks
 * @param ms Milliseconds to convert
 * @return Number of system ticks
 */
#define MS_TO_TICKS(ms) ((ms) * TICKS_PER_SECOND / 1000U)

Disadvantages of Using Macros

[!TIP] Use inline functions for complex logic instead of macros.

Explore More Topics

|πŸ‘ˆ Previous | Next πŸ‘‰|

Repositories

πŸš€ My Repositories

Contact & Discussion

If you have any thing would like to discuss or cooperate with me, please don’t hesitate to contact me via:

Home Page

🏠 Home