
首先我们来了解一下各个文件的作用是干什么的。
一、前言
在嵌入式开发中,STM32系列微控制器因其强大的性能和丰富的外设资源而广受欢迎。对于初学者和习惯于传统开发方式的工程师来说,标准库(Standard Peripheral Library)提供了一种直观且易于理解的编程接口。本文将详细介绍如何从零开始创建一个STM32F1/F4的标准库工程。
二、准备工作
2.1 所需工具和软件
-
开发板:STM32F103C8T6(F1系列)或STM32F407VET6(F4系列)
-
开发环境:
-
Keil MDK-ARM 或 IAR EWARM
-
或者免费的VSCode + ARM GCC工具链
-
-
软件包:
-
STM32标准外设库(STSW-STM32054/F1或STSW-STM32065/F4)
-
ST-Link驱动(用于调试下载)
-
2.2 标准库下载
可以从ST官网下载标准库,或从GitHub获取:
F1系列库
https://github.com/STMicroelectronics/STM32F1xx_StdPeriph_Lib
F4系列库
https://github.com/STMicroelectronics/STM32F4xx_StdPeriph_Driver
三、工程目录结构规划
创建一个清晰的目录结构是良好工程管理的基础:
My_STM32_Project/
├── CMSIS/ # Cortex微控制器软件接口标准
│ ├── core_cm3.c # F1系列使用
│ ├── core_cm4.c # F4系列使用
│ └── system_stm32f10x.c # 系统初始化
├── Libraries/
│ ├── STM32F10x_StdPeriph_Driver/ # F1外设驱动
│ │ ├── inc/
│ │ └── src/
│ └── STM32F4xx_StdPeriph_Driver/ # F4外设驱动
├── Project/
│ ├── MDK-ARM/ # Keil工程文件
│ └── src/
├── User/
│ ├── main.c
│ ├── stm32f10x_conf.h # F1配置文件
│ ├── stm32f4xx_conf.h # F4配置文件
│ ├── stm32f10x_it.c # 中断服务程序
│ └── stm32f10x_it.h
└── README.md
四、Keil MDK环境下创建工程(以STM32F103为例)
4.1 创建新工程
-
打开Keil,选择 Project → New μVision Project
-
选择工程保存路径,命名为
MyProject -
选择设备型号:STM32F103C8
4.2 添加文件组和源文件
在Project窗口右键点击Target,添加以下文件组:
-
Startup:启动文件
Libraries/CMSIS/startup_stm32f10x_md.s(根据容量选择)
-
CMSIS:核心文件
-
Libraries/CMSIS/core_cm3.c -
Libraries/CMSIS/system_stm32f10x.c
-
-
StdPeriph_Driver:标准外设驱动
- 添加常用外设:
misc.c,stm32f10x_gpio.c,stm32f10x_rcc.c
- 添加常用外设:
-
User:用户代码
-
User/main.c -
User/stm32f10x_it.c
-
4.3 配置工程选项
-
Target选项卡:
晶振频率:8.0MHz
Use MicroLIB:勾选(减小代码大小)
-
Output选项卡:
-
选择输出文件夹
-
勾选
Create HEX File
-
-
C/C++选项卡:
// 预定义宏(根据具体芯片修改)
STM32F10X_MD // 中等容量
USE_STDPERIPH_DRIVER // 使用标准库
// 包含路径
../Libraries/CMSIS
../Libraries/STM32F10x_StdPeriph_Driver/inc
../User
-
Debug选项卡:
-
选择ST-Link Debugger
-
勾选Reset and Run
-
五、编写基础代码
5.1 配置文件 stm32f10x_conf.h
cs
#ifndef __STM32F10x_CONF_H
#define __STM32F10x_CONF_H
// 取消注释以启用所需外设
#define _GPIO
#define _RCC
#define _USART
// #define _ADC
// #define _SPI
// #define _I2C
// #define _TIM
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "misc.h"
#endif /* __STM32F10x_CONF_H */
5.2 主函数模板 main.c
cs
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
void Delay(uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// 配置PC13为推挽输出(LED引脚)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
int main(void)
{
// 系统时钟初始化
SystemInit();
// GPIO配置
GPIO_Config();
while(1)
{
// LED闪烁
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄灭LED
Delay(0xFFFFF);
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 点亮LED
Delay(0xFFFFF);
}
}
5.3 系统时钟配置(重要!)
cs
void SystemClock_Config(void)
{
RCC_DeInit();
// 使能外部高速晶振
RCC_HSEConfig(RCC_HSE_ON);
// 等待HSE就绪
if(RCC_WaitForHSEStartUp() == SUCCESS)
{
// 设置PLL时钟源和倍频系数
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
// 使能PLL
RCC_PLLCmd(ENABLE);
// 等待PLL就绪
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 设置系统时钟源为PLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 等待系统时钟源切换完成
while(RCC_GetSYSCLKSource() != 0x08);
}
}
六、STM32F4与F1的主要差异
6.1 时钟配置差异
cs
// F4系列时钟配置示例
void SystemClock_Config_F4(void)
{
RCC_DeInit();
// 配置主PLL
RCC_PLLConfig(RCC_PLLSource_HSE, 8, 336, 2, 7);
// 使能主PLL
RCC_PLLCmd(ENABLE);
// 配置时钟分频
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div4);
RCC_PCLK2Config(RCC_HCLK_Div2);
}
6.2 外设操作差异
cs
// GPIO配置对比
// F1系列:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// F4系列:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
七、常见问题解决
7.1 编译错误处理
-
未定义标识符错误:检查头文件包含路径和宏定义
-
链接错误:确认所有必需的源文件都已添加到工程
-
容量选择错误:根据实际芯片选择正确的启动文件和宏定义
7.2 调试问题
-
无法下载程序:检查BOOT引脚配置
-
程序不运行:检查时钟配置是否正确
-
外设不工作:确认时钟已使能,引脚配置正确
八、工程优化建议
-
使用模块化编程:每个外设独立成模块
-
合理使用条件编译:
cs
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while(1);
}
#endif
- 创建统一的接口层:
cs
// bsp_led.h
#ifndef __BSP_LED_H
#define __BSP_LED_H
typedef enum
{
LED_OFF = 0,
LED_ON,
LED_TOGGLE
} LED_State;
void LED_Init(void);
void LED_Control(LED_State state);
#endif
九、从标准库迁移到HAL库的建议
虽然标准库易于理解,但ST已转向HAL/LL库。建议:
-
先掌握标准库,理解底层原理
-
逐步学习HAL库的架构
-
使用STM32CubeMX生成初始化代码
十、总结
创建STM32标准库工程虽然步骤较多,但每一步都是理解STM32架构的重要过程。通过手动创建工程,你可以:
-
深入理解启动过程
-
掌握时钟树配置
-
熟悉外设工作原理
-
建立良好的工程管理习惯
记住:嵌入式开发不仅仅是让代码运行,更重要的是理解每个配置背后的原理。标准库为这种理解提供了绝佳的入口。