跳到主要内容

【二】LED广告灯

1. 项目概述

本项目是一个基于STM32F103C8T6微控制器的LED广告灯示例程序,实现了10种不同的LED动态显示效果。项目使用STM32标准外设库(V3.5.0)开发,展示了丰富多样的LED控制模式。


2. 硬件平台

  • MCU型号:STM32F103C8T6
  • 核心架构:ARM Cortex-M3
  • 开发板:成都烁云科技开发板
  • LED连接:GPIOB端口(PB0~PB7)
  • LED数量:8个
  • 标准库版本:STM32F10x Standard Peripheral Library V3.5.0

3. 文件结构

2_LSD广告灯/
├── User/ # 用户代码目录
│ ├── main.c # 主程序文件 - 演示各种广告灯效果
│ ├── stm32f10x_conf.h # 标准库配置文件
│ ├── stm32f10x_it.c # 中断服务函数
│ ├── stm32f10x_it.h # 中断头文件
│ ├── system_stm32f10x.c # 系统初始化文件
│ └── TF_App/ # 应用层驱动
│ ├── TF_LED.c # LED驱动实现(含10种特效)
│ ├── 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)

主程序循环演示10种不同的LED广告灯效果,每种效果运行后有500ms或1000ms的间隔。

演示效果列表:

序号效果名称函数调用延时(ms)循环次数
1流水灯(点亮后熄灭)LED_Effect_Flow(100, 3)1003
2流水灯(依次点亮不熄灭)LED_Effect_Flow_NoOff(100, 2)1002
3跑马灯(单灯左右移动)LED_Effect_RunningLight(80, 2)802
4全部闪烁LED_Effect_Blink(200, 5)2005
5奇偶交替闪烁LED_Effect_Alternate(150, 5)1505
6中间扩散LED_Effect_MiddleSpread(100, 3)1003
7波浪效果LED_Effect_Wave(100, 3)1003
8流星效果(拖尾)LED_Effect_Meteor(80, 2)802
9二进制计数(0-255)LED_Effect_BinaryCount(50, 1)501
10随机闪烁LED_Effect_Twinkle(100, 20)10020

单一效果运行模式:

主程序中提供了注释代码,可以选择单一效果持续运行:

int main(void)
{
LED_Init();
LED_Effect_BinaryCount(50, 0); // 0表示无限循环
}

4.2 LED驱动模块 (TF_LED.c/h)

提供LED的初始化、基础控制和10种广告灯效果功能。

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

实现: 使用switch-case语句,通过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

实现: 使用switch-case语句,通过GPIO_ResetBits()函数将对应引脚拉低

示例:

LED_Off(1);  // 熄灭LED1
LED_Off(0); // 熄灭所有LED

void LED_Toggle(unsigned char Led_PIN)

功能: 翻转指定LED的状态(亮变灭,灭变亮)

参数:

  • Led_PIN: LED编号(1-8)

实现原理:

  1. 通过GPIO_ReadOutputDataBit()读取当前LED状态
  2. 如果为高电平则拉低,如果为低电平则拉高
  3. 对每个LED使用独立的case分支处理

示例:

LED_Toggle(1);  // LED1状态翻转

void LED_AllOff(void)

功能: 关闭所有LED

实现:

GPIO_ResetBits(LED_PORT, LED_PIN);

void LED_AllOn(void)

功能: 点亮所有LED

实现:

GPIO_SetBits(LED_PORT, LED_PIN);

4.2.3 广告灯效果函数详解

1. LED_Effect_Flow() - 流水灯效果

函数原型:

void LED_Effect_Flow(unsigned int delay_ms, unsigned int loops)

功能描述: 8个LED依次点亮后立即熄灭,形成流水效果

参数:

  • delay_ms: 每个LED之间的延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

for i = 1 to 8:
点亮LED[i]
延时delay_ms
熄灭LED[i]

效果演示: ●○○○○○○○ → ○●○○○○○○ → ... → ○○○○○○○●


2. LED_Effect_Flow_NoOff() - 流水灯效果(不熄灭)

函数原型:

void LED_Effect_Flow_NoOff(unsigned int delay_ms, unsigned int loops)

功能描述: 8个LED依次点亮,点亮后保持,全部点亮后一起熄灭

参数:

  • delay_ms: 每个LED之间的延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

全部熄灭
for i = 1 to 8:
点亮LED[i]
延时delay_ms
全部熄灭

效果演示: ●○○○○○○○ → ●●○○○○○○ → ... → ●●●●●●●● → ○○○○○○○○


3. LED_Effect_RunningLight() - 跑马灯效果

函数原型:

void LED_Effect_RunningLight(unsigned int delay_ms, unsigned int loops)

功能描述: 单个LED从左到右、再从右到左来回移动

参数:

  • delay_ms: 移动延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

// 从左到右
for i = 1 to 8:
全部熄灭
点亮LED[i]
延时delay_ms

// 从右到左
for i = 7 down to 1:
全部熄灭
点亮LED[i]
延时delay_ms

效果演示: ●○○○○○○○ → ○●○○○○○○ → ... → ○○○○○○○● → ○○○○○○●○ → ...


函数原型:

void LED_Effect_Blink(unsigned int delay_ms, unsigned int loops)

功能描述: 所有LED同时闪烁

参数:

  • delay_ms: 闪烁延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

全部点亮
延时delay_ms
全部熄灭
延时delay_ms

效果演示: ●●●●●●●● ↔ ○○○○○○○○


5. LED_Effect_Alternate() - 奇偶交替闪烁

函数原型:

void LED_Effect_Alternate(unsigned int delay_ms, unsigned int loops)

功能描述: 奇数位LED和偶数位LED交替闪烁

参数:

  • delay_ms: 闪烁延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

// 奇数LED点亮
点亮LED1, LED3, LED5, LED7
延时delay_ms

// 偶数LED点亮
点亮LED2, LED4, LED6, LED8
延时delay_ms

效果演示: ●○●○●○●○ ↔ ○●○●○●○●


6. LED_Effect_MiddleSpread() - 中间扩散效果

函数原型:

void LED_Effect_MiddleSpread(unsigned int delay_ms, unsigned int loops)

功能描述: LED从中间向两边逐步扩散点亮

参数:

  • delay_ms: 延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

全部熄灭
点亮LED4, LED5 // ○○○●●○○○
点亮LED3, LED6 // ○○●●●●○○
点亮LED2, LED7 // ○●●●●●●○
点亮LED1, LED8 // ●●●●●●●●
全部熄灭

效果演示: ○○○●●○○○ → ○○●●●●○○ → ○●●●●●●○ → ●●●●●●●●


7. LED_Effect_Wave() - 波浪效果

函数原型:

void LED_Effect_Wave(unsigned int delay_ms, unsigned int loops)

功能描述: 3个连续的LED作为一组向右移动

参数:

  • delay_ms: 延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

点亮LED1,2,3             // ●●●○○○○○
点亮LED2,3,4 // ○●●●○○○○
点亮LED3,4,5 // ○○●●●○○○
点亮LED4,5,6 // ○○○●●●○○
点亮LED5,6,7 // ○○○○●●●○
点亮LED6,7,8 // ○○○○○●●●

效果演示: 形成波浪向右推进的视觉效果


8. LED_Effect_Meteor() - 流星效果

函数原型:

void LED_Effect_Meteor(unsigned int delay_ms, unsigned int loops)

功能描述: LED拖尾点亮,形成流星划过的效果

参数:

  • delay_ms: 延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

点亮LED1                 // ●○○○○○○○
点亮LED2 // ●●○○○○○○
熄灭LED1,点亮LED3 // ○●●○○○○○
熄灭LED2,点亮LED4 // ○○●●○○○○
... 以此类推

效果特点: 当前LED点亮时,前一个LED保持点亮一个周期后熄灭,形成拖尾效果


9. LED_Effect_BinaryCount() - 二进制计数效果

函数原型:

void LED_Effect_BinaryCount(unsigned int delay_ms, unsigned int loops)

功能描述: 8个LED以二进制方式显示0-255的计数

参数:

  • delay_ms: 计数延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

for i = 0 to 255:
全部熄灭
if (i & 0x01) 点亮LED1 // 第0位
if (i & 0x02) 点亮LED2 // 第1位
if (i & 0x04) 点亮LED3 // 第2位
if (i & 0x08) 点亮LED4 // 第3位
if (i & 0x10) 点亮LED5 // 第4位
if (i & 0x20) 点亮LED6 // 第5位
if (i & 0x40) 点亮LED7 // 第6位
if (i & 0x80) 点亮LED8 // 第7位
延时delay_ms

效果演示:

  • 0: ○○○○○○○○
  • 1: ●○○○○○○○
  • 2: ○●○○○○○○
  • 3: ●●○○○○○○
  • ...
  • 255: ●●●●●●●●

应用场景: 可用于学习二进制计数原理,或作为数字显示


10. LED_Effect_Twinkle() - 随机闪烁效果

函数原型:

void LED_Effect_Twinkle(unsigned int delay_ms, unsigned int loops)

功能描述: 所有LED同时进行状态翻转,形成随机闪烁效果

参数:

  • delay_ms: 闪烁延时时间(毫秒)
  • loops: 循环次数,0表示无限循环

实现逻辑:

for i = 1 to 8:
翻转LED[i]状态
延时delay_ms

效果特点: 每次循环所有LED同时翻转状态,产生动态闪烁效果


4.3 延时模块 (TF_Delay.c/h)

提供软件延时和SysTick硬件延时两种方式。

4.3.1 软件延时函数

void Delay_nus(unsigned long n)

功能: 微秒级软件延时

参数:

  • n: 延时时间(单位:微秒),1表示1us,1000表示1ms

实现:

void Delay_nus(unsigned long n)
{
unsigned long j;
while(n--)
{
j=12;
while(j--);
}
}

特点:

  • 简单的循环延时实现
  • 精度取决于系统时钟和编译优化
  • 占用CPU资源

void Delay_nms(unsigned long n)

功能: 毫秒级软件延时

参数:

  • n: 延时时间(单位:毫秒),1表示1ms,1000表示1s

实现:

void Delay_nms(unsigned long n)
{
while(n--)
Delay_nus(1030); // 调用1030us延时补偿函数调用开销
}

注意: 使用1030us而非1000us是为了补偿函数调用开销


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()中断服务函数中调用,用于递减延时计数器。

实现:

void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}

5. 使用示例

5.1 运行所有效果(默认模式)

int main(void)
{
LED_Init();

while(1)
{
// 依次演示10种效果
LED_Effect_Flow(100, 3);
Delay_nms(500);

LED_Effect_Flow_NoOff(100, 2);
Delay_nms(500);

LED_Effect_RunningLight(80, 2);
Delay_nms(500);

LED_Effect_Blink(200, 5);
Delay_nms(500);

LED_Effect_Alternate(150, 5);
Delay_nms(500);

LED_Effect_MiddleSpread(100, 3);
Delay_nms(500);

LED_Effect_Wave(100, 3);
Delay_nms(500);

LED_Effect_Meteor(80, 2);
Delay_nms(500);

LED_Effect_BinaryCount(50, 1);
Delay_nms(500);

LED_Effect_Twinkle(100, 20);
Delay_nms(1000);
}
}

5.2 单一效果持续运行

int main(void)
{
LED_Init();

// 选择一种效果持续运行(无限循环)
LED_Effect_RunningLight(80, 0); // 0表示无限循环
}

5.3 自定义效果组合

int main(void)
{
LED_Init();

while(1)
{
// 快速流水灯
LED_Effect_Flow(50, 5);
Delay_nms(200);

// 慢速跑马灯
LED_Effect_RunningLight(150, 3);
Delay_nms(200);

// 快速闪烁
LED_Effect_Blink(100, 10);
Delay_nms(500);
}
}

5.4 使用基础控制函数自定义效果

int main(void)
{
LED_Init();
uint8_t i;

while(1)
{
// 自定义效果:双向流水灯
for(i = 1; i <= 4; i++)
{
LED_On(i);
LED_On(9-i);
Delay_nms(100);
LED_Off(i);
LED_Off(9-i);
}

Delay_nms(500);
}
}

6. 效果函数参数调优指南

6.1 延时参数建议

效果类型推荐延时范围最佳视觉效果说明
流水灯50-200ms80-100ms太快看不清,太慢显得拖沓
跑马灯50-150ms70-90ms需要较快速度体现动感
闪烁100-500ms150-250ms人眼舒适的闪烁频率
奇偶交替100-300ms150-200ms切换不宜过快
中间扩散80-200ms100-120ms逐级扩散需要清晰
波浪50-150ms80-100ms快速流动更有动感
流星50-120ms70-90ms快速才有流星感觉
二进制计数30-100ms40-60ms计数太慢会很长时间
随机闪烁50-200ms100-150ms适中的闪烁频率

6.2 循环次数建议

效果类型建议循环次数总时长估算说明
流水灯2-5次约2-5秒完整展示效果
跑马灯2-4次约3-6秒来回一次为一个循环
闪烁3-10次约1-5秒几次闪烁即可
奇偶交替3-8次约1-5秒交替几次体现效果
中间扩散2-5次约2-5秒扩散收缩过程
波浪2-5次约2-5秒波浪流动过程
流星1-3次约2-6秒完整流动过程
二进制计数1-2次约13-26秒0-255需要较长时间
随机闪烁10-30次约1-6秒多次闪烁才有效果

6.3 效果组合建议

模式A:展示模式(默认)

  • 适用场景:产品展示、功能演示
  • 特点:每种效果运行适中次数,全面展示所有功能
  • 总循环时间:约40-60秒

模式B:快速循环模式

LED_Effect_Flow(50, 2);
LED_Effect_RunningLight(60, 1);
LED_Effect_Blink(100, 3);
LED_Effect_Alternate(100, 3);
  • 适用场景:商业广告、吸引注意
  • 特点:快速切换,节奏紧凑
  • 总循环时间:约10-15秒

模式C:单一循环模式

LED_Effect_RunningLight(80, 0);  // 无限循环
  • 适用场景:简单装饰、持续运行
  • 特点:专注单一效果,循环播放

7. 重要注意事项

7.1 JTAG调试接口重映射

  • 程序中禁用了JTAG功能,保留了SWD调试接口
  • 这使得PB3、PB4引脚可以作为普通GPIO使用
  • 调试时请使用SWD接口,不要使用JTAG接口

7.2 时钟配置

  • LED驱动使用的是GPIOB,时钟为APB2总线
  • 必须在使用GPIO前使能相应的时钟
  • 已在LED_Init()中自动配置

7.3 代码中发现的问题

问题1:LED引脚时钟宏定义错误

位置: TF_LED.h (第33、37、41、45、49、53、57、61行)

问题描述:

#define LED1_CLK   RCC_APB2Periph_GPIOC  // 错误:应该是GPIOB
#define LED2_CLK RCC_APB2Periph_GPIOC // 错误:应该是GPIOB
// ... LED3~LED8同样的问题

影响: 由于实际使用的是LED_CLK宏(已正确定义为RCC_APB2Periph_GPIOB),所以不影响程序运行。

建议修改:

#define LED1_CLK   RCC_APB2Periph_GPIOB
#define LED2_CLK RCC_APB2Periph_GPIOB
#define LED3_CLK RCC_APB2Periph_GPIOB
#define LED4_CLK RCC_APB2Periph_GPIOB
#define LED5_CLK RCC_APB2Periph_GPIOB
#define LED6_CLK RCC_APB2Periph_GPIOB
#define LED7_CLK RCC_APB2Periph_GPIOB
#define LED8_CLK RCC_APB2Periph_GPIOB

7.4 延时函数选择建议

场景推荐函数原因
普通延时Delay_nms()简单易用,不占用外设资源
精确延时SysTick_Delay_ms()精度高,不受系统负载影响
长时间延时Delay_nms()避免长时间关闭中断
微秒级延时Delay_nus()简单快速

注意: 本项目所有效果函数都使用Delay_nms(),如需高精度可以修改为SysTick_Delay_ms()


7.5 CPU占用说明

  • 所有效果函数都是阻塞式运行
  • 运行期间CPU一直处于工作状态
  • 如需后台运行,建议使用定时器中断实现

8. 编译和下载

8.1 开发环境

  • IDE: Keil MDK-ARM (μVision)
  • 编译器: ARMCC
  • 调试器: J-Link / ST-Link
  • 下载接口: SWD

8.2 工程配置

工程文件位于 MDK/TEST_CODE.uvproj

关键配置:

  • Target Device: STM32F103C8
  • Crystal: 8.0 MHz
  • Use MicroLIB: 推荐开启(减小代码体积)
  • Optimization Level: Level 1

8.3 下载步骤

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

8.4 编译输出

编译成功后可在 MDK/Output/ 目录下找到以下文件:

  • .hex: 可下载的十六进制文件
  • .bin: 二进制文件
  • .axf: 调试文件

9. 常见问题

Q1: LED不亮或只有部分亮?

检查项:

  1. 确认硬件连接正确(PB0~PB7)
  2. 检查LED极性(共阴/共阳)
  3. 确认GPIO时钟已使能
  4. 用万用表测量GPIO引脚电平
  5. 检查程序是否正确下载

解决方案:

// 在main函数开始添加测试代码
LED_Init();
LED_AllOn(); // 如果都能亮,说明硬件正常
Delay_nms(2000);
LED_AllOff();

Q2: 效果运行速度不正常?

可能原因:

  1. 系统时钟配置不正确
  2. 延时参数设置不合理
  3. 编译优化等级影响延时精度

解决方案:

  1. 检查system_stm32f10x.c中的时钟配置
  2. 调整delay_ms参数
  3. 使用SysTick硬件延时提高精度

Q3: 下载失败?

解决方案:

  1. 检查调试器连接
  2. 确认使用SWD接口(不要用JTAG)
  3. 尝试在上电时按住复位键,然后下载
  4. 检查供电是否正常(3.3V)
  5. 使用Keil的"Erase Full Chip"功能

Q4: 效果切换时有短暂停顿?

原因: Delay_nms(500)Delay_nms(1000) 间隔时间

解决方案:

// 减少或去除效果间的延时
LED_Effect_Flow(100, 3);
// Delay_nms(500); // 注释掉此行
LED_Effect_Flow_NoOff(100, 2);

Q5: 如何添加自定义效果?

步骤:

  1. TF_LED.h中添加函数声明:
void LED_Effect_Custom(unsigned int delay_ms, unsigned int loops);
  1. TF_LED.c中实现函数:
void LED_Effect_Custom(unsigned int delay_ms, unsigned int loops)
{
unsigned int count = 0;

while(loops == 0 || count < loops)
{
// 在这里编写自定义效果逻辑
LED_On(1);
Delay_nms(delay_ms);
LED_Off(1);

count++;
}
}
  1. main.c中调用:
LED_Effect_Custom(100, 5);

Q6: 如何实现非阻塞式效果?

当前实现: 所有效果函数都是阻塞式的

改进方案: 使用定时器+状态机实现

// 示例:使用定时器中断实现非阻塞流水灯
volatile uint8_t led_index = 1;

void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
LED_AllOff();
LED_On(led_index);
led_index++;
if(led_index > 8) led_index = 1;

TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}