Quick Answer
A well-organized STM32 project should have separate directories for drivers, middleware, application code, and configuration; modular design with clear interfaces; consistent naming conventions; and comprehensive documentation. Use HAL for peripheral abstraction, isolate hardware-dependent code, and implement layered architecture. This approach improves maintainability, enables code reuse, and facilitates team collaboration.
Introduction
Good project structure and code organization are essential for developing maintainable, scalable embedded systems. As projects grow in complexity, proper organization becomes increasingly important for managing dependencies, enabling code reuse, and facilitating team collaboration. This guide presents best practices for structuring STM32 projects.
Core Content
1. Directory Structure
Recommended Structure
project/ ├── Core/ │ ├── Inc/ # HAL initialization headers │ └── Src/ # HAL initialization source ├── Drivers/ │ ├── STM32F4xx_HAL_Driver/ │ ├── CMSIS/ │ └── BSP/ # Board support packages ├── Middlewares/ │ ├── FreeRTOS/ │ ├── USB_Device/ │ └── FatFS/ ├── App/ │ ├── Inc/ # Application headers │ │ ├── main.h │ │ ├── config.h │ │ └── app_tasks.h │ └── Src/ # Application source │ ├── main.c │ ├── app_tasks.c │ └── sensor_handler.c ├── Hardware/ │ ├── Inc/ # Hardware abstraction │ │ ├── motor_driver.h │ │ └── sensor_interface.h │ └── Src/ │ ├── motor_driver.c │ └── sensor_interface.c ├── Utils/ │ ├── Inc/ # Utility functions │ │ ├── circular_buffer.h │ │ └── debug_print.h │ └── Src/ │ ├── circular_buffer.c │ └── debug_print.c ├── Build/ # Build outputs ├── Doc/ # Documentation └── Tests/ # Unit tests
2. Modular Design Principles
Separation of Concerns
- Hardware Layer: Direct peripheral access, BSP functions
- Driver Layer: Abstract hardware details, provide clean APIs
- Middleware Layer: RTOS, file systems, communication stacks
- Application Layer: Business logic, state machines, algorithms
Interface Design
// Good: Hardware-independent interface
// motor_driver.h
typedef struct {
void (*setSpeed)(uint8_t speed);
void (*setDirection)(bool forward);
void (*stop)(void);
} MotorDriver;
// Application code uses interface, not implementation
MotorDriver motor = MotorDriver_Init();
motor.setSpeed(50);
3. Naming Conventions
Consistent Naming
- Functions: Module_Action (e.g., Motor_SetSpeed, UART_SendData)
- Variables: camelCase (e.g., motorSpeed, sensorValue)
- Constants: UPPER_SNAKE_CASE (e.g., MAX_BUFFER_SIZE, DEFAULT_TIMEOUT)
- Types: PascalCase with _t suffix (e.g., MotorConfig_t, SensorData_t)
- Macros: UPPER_SNAKE_CASE (e.g., ENABLE_DEBUG, CALCULATE_AVERAGE(x,y))
4. Configuration Management
Centralized Configuration
// config.h - Centralized configuration #ifndef CONFIG_H #define CONFIG_H // System configuration #define SYSTEM_CLOCK_HZ 168000000 #define TICK_RATE_HZ 1000 // Peripheral configuration #define UART_BAUD_RATE 115200 #define SPI_CLOCK_SPEED_HZ 1000000 // Application configuration #define SENSOR_SAMPLE_RATE_MS 100 #define MOTOR_PWM_FREQUENCY_HZ 20000 // Feature toggles #define ENABLE_DEBUG_PRINT 1 #define ENABLE_WATCHDOG 1 #endif
5. Documentation Practices
Code Documentation
/** * @brief Initialize the motor driver * @param config Pointer to motor configuration structure * @retval MotorStatus_t Status of initialization * * @note This function must be called before any other motor functions * @warning Do not call from ISR context */ MotorStatus_t Motor_Init(MotorConfig_t *config);
6. Version Control
.gitignore for STM32
# Build outputs Build/ *.o *.elf *.hex *.bin *.map # IDE files .vscode/ .idea/ *.uvprojx.user *.ioc # Generated files Drivers/STM32F4xx_HAL_Driver/
FAQ
Should I use HAL or write my own drivers?
Use HAL for most projects. HAL provides tested, portable code that speeds development. Write custom drivers only when you need performance optimization or features not available in HAL.
How do I handle multiple build configurations?
Use preprocessor defines. Create Debug and Release configurations in your IDE with different optimization levels and defines (e.g., DEBUG, RELEASE). Use #ifdef to enable/disable features.
Should I use a static analysis tool?
Yes! Static analysis tools (clang-tidy, Cppcheck, PC-lint) catch bugs early and enforce coding standards. Integrate them into your build system for automatic checking.
Conclusion
Well-organized STM32 projects:
- Directory Structure: Clear separation of modules
- Modular Design: Hardware-independent interfaces
- Naming Conventions: Consistent across the project
- Configuration: Centralized, easy to modify
- Documentation: Clear, complete, and maintained
- Version Control: Proper .gitignore, meaningful commits
Need Help with Project Architecture?
InnovChip provides embedded software architecture and development services. Contact us today for assistance with project structure, code review, and professional firmware development.
