【一】LED闪烁灯
1. 项目概述
本项目是一个基于STM32F103C8T6微控制器的LED闪烁示例程序,演示了GPIO控制LED的基本操作。项目使用STM32标准外设库(V3.5.0)开发。
2. 硬件平台
- MCU型号:STM32F103C8T6
- 核心架构:ARM Cortex-M3
- 开发板:成都烁云科技开发板
- LED连接:GPIOB端口(PB0~PB7)
- 标准库版本:STM32F10x Standard Peripheral Library V3.5.0
3. 文件结构
1_SSD闪烁灯/
├── User/ # 用户代码目录
│ ├── main.c # 主程序文件
│ ├── stm32f10x_conf.h # 标准库配置文件
│ ├── stm32f10x_it.c # 中断服务函数
│ ├── stm32f10x_it.h # 中断头文件
│ ├── system_stm32f10x.c # 系统初始化文件
│ └── TF_App/ # 应用层驱动
│ ├── TF_LED.c # LED驱动实现
│ ├── TF_LED.h # LED驱动头文件
│ ├── TF_Delay.c # 延时函数实现
│ └── TF_Delay.h # 延时函数头文件
├── Libraries/ # STM32标准外设库
│ ├── CMSIS/ # ARM CMSIS核心支持
│ └── STM32F10x_StdPeriph_Driver/ # 标准外设驱动
├── MDK/ # Keil工程文件
└── Readme/ # 说明文档
4. 核心模块功能
4.1 主程序模块 (main.c)
主程序实现了LED的周期性闪烁功能。
主要功能:
- 初始化LED模块
- 循环控制LED0闪烁,周期为200ms(亮100ms,灭100ms)
代码流程:
int main(void)
{
LED_Init(); // 初始化LED
while(1)
{
LED_On(0); // 点亮LED0
Delay_nms(100); // 延时100ms
LED_Off(0); // 熄灭LED0
Delay_nms(100); // 延时100ms
}
}
4.2 LED驱动模块 (TF_LED.c/h)
提供LED的初始化和控制功能。
4.2.1 硬件引脚定义
| LED编号 | GPIO引脚 | GPIO端口 | 时钟 |
|---|---|---|---|
| LED1 | GPIO_Pin_0 | GPIOB | RCC_APB2Periph_GPIOB |
| LED2 | GPIO_Pin_1 | GPIOB | RCC_APB2Periph_GPIOB |
| LED3 | GPIO_Pin_2 | GPIOB | RCC_APB2Periph_GPIOB |
| LED4 | GPIO_Pin_3 | GPIOB | RCC_APB2Periph_GPIOB |
| LED5 | GPIO_Pin_4 | GPIOB | RCC_APB2Periph_GPIOB |
| LED6 | GPIO_Pin_5 | GPIOB | RCC_APB2Periph_GPIOB |
| LED7 | GPIO_Pin_6 | GPIOB | RCC_APB2Periph_GPIOB |
| LED8 | GPIO_Pin_7 | GPIOB | RCC_APB2Periph_GPIOB |
注意: 头文件中LED1~LED8的时钟宏定义存在错误(写成了RCC_APB2Periph_GPIOC),但在LED_Init()函数中使用了正确的LED_CLK宏定义,因此不影响实际运行。
4.2.2 API函数说明
void LED_Init(void)
功能: 初始化LED所用的GPIO端口
实现细节:
- 使能GPIOB端口时钟
- 使能AFIO时钟并禁用JTAG功能(释放PB3、PB4引脚)
- 配置PB0~PB7为推挽输出模式,速度10MHz
- 初始化后所有LED熄灭
JTAG重映射说明:
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
此配置禁用JTAG-DP功能,保留SW-DP调试功能,使PB3、PB4可作为普通GPIO使用。
void LED_On(unsigned char Led_PIN)
功能: 点亮指定的LED
参数:
Led_PIN: LED编号(1-8),传入0或其他值则点亮所有LED
实现: 通过GPIO_SetBits()函数将对应引脚拉高
示例:
LED_On(1); // 点亮LED1
LED_On(0); // 点亮所有LED
void LED_Off(unsigned char Led_PIN)
功能: 熄灭指定的LED
参数:
Led_PIN: LED编号(1-8),传入0或其他值则熄灭所有LED
实现: 通过GPIO_ResetBits()函数将对应引脚拉低
示例:
LED_Off(1); // 熄灭LED1
LED_Off(0); // 熄灭所有LED
void LED_Covert(unsigned char Led_PIN)
功能: 翻转指定LED的状态
说明: 此函数在头文件中有声明,但在.c文件中未实现。
4.3 延时模块 (TF_Delay.c/h)
提供软件延时和SysTick硬件延时两种方式。
4.3.1 软件延时函数
void Delay_nus(unsigned long n)
功能: 微秒级软件延时
参数:
n: 延时时间(单位:微秒),1表示1us,1000表示1ms
特点:
- 简单的循环延时实现
- 精度取决于系统时钟和编译优化
- 占用CPU资源
void Delay_nms(unsigned long n)
功能: 毫秒级软件延时
参数:
n: 延时时间(单位:毫秒),1表示1ms,1000表示1s
实现:
void Delay_nms(unsigned long n)
{
while(n--)
Delay_nus(1030); // 调用1030us延时补偿函数调用开销
}
4.3.2 SysTick硬件延时函数
void SysTick_Delay_ms(__IO uint32_t nTime)
功能: 基于SysTick的毫秒级精确延时
参数:
nTime: 延时时间(单位:毫秒)
特点:
- 使用SysTick中断实现
- 延时精度高
- 1ms产生一次SysTick中断
实现原理:
void SysTick_Delay_ms(__IO uint32_t nTime)
{
while(SysTick_Config(SystemCoreClock/1000)); // 配置1ms中断一次
TimingDelay = nTime;
while(TimingDelay != 0); // 等待计数完成
SysTick->CTRL = 0x00; // 关闭计数器
SysTick->VAL = 0x00; // 清空计数器
}
void SysTick_Delay_us(__IO uint32_t nTime)
功能: 基于SysTick的微秒级精确延时
参数:
nTime: 延时时间(单位:微秒)
特点:
- 使用SysTick中断实现
- 1us产生一次SysTick中断
- 适合短时间精确延时
void SysTick_Delay(__IO uint32_t nTime)
功能: 通用SysTick延时函数
参数:
nTime: 延时计数值(配合SysTick_Config使用)
void TimingDelay_Decrement(void)
功能: SysTick中断中调用的计数递减函数
说明: 在SysTick_Handler()中断服务函数中调用,用于递减延时计数器。
5. 使用示例
5.1 基本LED闪烁
#include "TF_LED.h"
#include "TF_Delay.h"
int main(void)
{
LED_Init();
while(1)
{
LED_On(1); // 点亮LED1
Delay_nms(500); // 延时500ms
LED_Off(1); // 熄灭LED1
Delay_nms(500); // 延时500ms
}
}
5.2 多个LED流水灯
int main(void)
{
LED_Init();
uint8_t i;
while(1)
{
for(i = 1; i <= 8; i++)
{
LED_On(i);
Delay_nms(100);
LED_Off(i);
}
}
}
5.3 使用SysTick延时
int main(void)
{
LED_Init();
while(1)
{
LED_On(1);
SysTick_Delay_ms(1000); // SysTick精确延时1秒
LED_Off(1);
SysTick_Delay_ms(1000);
}
}
6. 重要注意事项
6.1 JTAG调试接口重映射
- 程序中禁用了JTAG功能,保留了SWD调试接口
- 这使得PB3、PB4引脚可以作为普通GPIO使用
- 调试时请使用SWD接口,不要使用JTAG接口
6.2 时钟配置
- LED驱动使用的是GPIOB,时钟为APB2总线
- 必须在使用GPIO前使能相应的时钟
6.3 代码中发现的问题
问题1:LED引脚时钟宏定义错误
位置: TF_LED.h (第33、37、41、45、49、53、57、61行)
问题描述:
#define LED1_CLK RCC_APB2Periph_GPIOC // 错误:应该是GPIOB
影响: 由于实际使用的是LED_CLK宏(已正确定义为RCC_APB2Periph_GPIOB),所以不影响程序运行。
建议修改:
#define LED1_CLK RCC_APB2Periph_GPIOB
#define LED2_CLK RCC_APB2Periph_GPIOB
// ... 其他LED时钟定义同理
问题2:未实现的函数
位置: TF_LED.h声明了LED_Covert()函数,但TF_LED.c中未实现
建议: 在TF_LED.c中添加实现:
void LED_Covert(unsigned char Led_PIN)
{
switch(Led_PIN)
{
case 1:
if(GPIO_ReadOutputDataBit(LED1_PORT, LED1_PIN))
GPIO_ResetBits(LED1_PORT, LED1_PIN);
else
GPIO_SetBits(LED1_PORT, LED1_PIN);
break;
// ... 其他LED同理
default:
GPIO_WriteBit(LED_PORT, LED_PIN,
(BitAction)(1 - GPIO_ReadOutputData(LED_PORT)));
break;
}
}
6.4 延时函数选择建议
| 场景 | 推荐函数 | 原因 |
|---|---|---|
| 普通延时 | Delay_nms() | 简单易用,不占用外设资源 |
| 精确延时 | SysTick_Delay_ms() | 精度高,不受系统负载影响 |
| 长时间延时 | Delay_nms() | 避免长时间关闭中断 |
| 微秒级延时 | Delay_nus() | 简单快速 |
7. 编译和下载
7.1 开发环境
- IDE: Keil MDK-ARM (μVision)
- 编译器: ARMCC
- 调试器: J-Link / ST-Link
- 下载接口: SWD
7.2 工程配置
工程文件位于 MDK/TEST_CODE.uvproj
关键配置:
- Target Device: STM32F103C8
- Crystal: 8.0 MHz
- Use MicroLIB: 推荐开启(减小代码体积)
- Optimization Level: Level 1
7.3 下载步骤
- 打开工程文件
MDK/TEST_CODE.uvproj - 编译工程(F7)
- 连接调试器到开发板的SWD接口
- 下载程序(F8)
- 运行程序,观察LED闪烁
8. 常见问题
Q1: LED不亮?
检查项:
- 确认硬件连接正确
- 检查LED极性(共阴/共阳)
- 确认GPIO时钟已使能
- 用万用表测量GPIO引脚电平
Q2: 下载失败?
解决方案:
- 检查调试器连接
- 确认使用SWD接口(不要用JTAG)
- 尝试在上电时按住复位键,然后下载
- 检查供电是否正常
Q3: 延时不准确?
可能原因:
- 系统时钟配置不正确
- 编译器优化等级影响软件延时
- 建议使用SysTick硬件延时
Q4: 调试时无法连接?
原因: JTAG功能被禁用
解决方案: 使用SWD调试接口