跳到主要内容

【一】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端口时钟
LED1GPIO_Pin_0GPIOBRCC_APB2Periph_GPIOB
LED2GPIO_Pin_1GPIOBRCC_APB2Periph_GPIOB
LED3GPIO_Pin_2GPIOBRCC_APB2Periph_GPIOB
LED4GPIO_Pin_3GPIOBRCC_APB2Periph_GPIOB
LED5GPIO_Pin_4GPIOBRCC_APB2Periph_GPIOB
LED6GPIO_Pin_5GPIOBRCC_APB2Periph_GPIOB
LED7GPIO_Pin_6GPIOBRCC_APB2Periph_GPIOB
LED8GPIO_Pin_7GPIOBRCC_APB2Periph_GPIOB

注意: 头文件中LED1~LED8的时钟宏定义存在错误(写成了RCC_APB2Periph_GPIOC),但在LED_Init()函数中使用了正确的LED_CLK宏定义,因此不影响实际运行。

4.2.2 API函数说明

void LED_Init(void)

功能: 初始化LED所用的GPIO端口

实现细节:

  1. 使能GPIOB端口时钟
  2. 使能AFIO时钟并禁用JTAG功能(释放PB3、PB4引脚)
  3. 配置PB0~PB7为推挽输出模式,速度10MHz
  4. 初始化后所有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 下载步骤

  1. 打开工程文件 MDK/TEST_CODE.uvproj
  2. 编译工程(F7)
  3. 连接调试器到开发板的SWD接口
  4. 下载程序(F8)
  5. 运行程序,观察LED闪烁

8. 常见问题

Q1: LED不亮?

检查项:

  1. 确认硬件连接正确
  2. 检查LED极性(共阴/共阳)
  3. 确认GPIO时钟已使能
  4. 用万用表测量GPIO引脚电平

Q2: 下载失败?

解决方案:

  1. 检查调试器连接
  2. 确认使用SWD接口(不要用JTAG)
  3. 尝试在上电时按住复位键,然后下载
  4. 检查供电是否正常

Q3: 延时不准确?

可能原因:

  1. 系统时钟配置不正确
  2. 编译器优化等级影响软件延时
  3. 建议使用SysTick硬件延时

Q4: 调试时无法连接?

原因: JTAG功能被禁用

解决方案: 使用SWD调试接口