一、实验目标
使用 STM32F407 控制板载 RGB LED,让红、绿、蓝三种颜色依次点亮,每种颜色保持 500ms,然后循环。
本实验使用 STM32 标准外设库。代码分为三部分:
led目录:配置 GPIO,提供点亮和熄灭接口。delay目录:使用 SysTick 实现毫秒延时。User/main.c:决定亮灯顺序和保持时间。
这里的“流水”是一颗 RGB 灯依次切换颜色。如果要实现一排 LED 逐颗移动,还需要多个独立可控的灯。
二、先看原理图:为什么低电平点亮?
本实验所用板卡资料中,RGB LED 的三个通道连接如下:
| 颜色 | STM32 引脚 | 点亮电平 | 熄灭电平 |
|---|---|---|---|
| 红 | PF6 | 低 | 高 |
| 绿 | PF7 | 低 | 高 |
| 蓝 | PF8 | 低 | 高 |
这是共阳 RGB LED。它的公共正极接电源,三个颜色通道的负极分别连接 GPIO。当某个 GPIO 输出低电平时,对应通道形成电流路径,LED 点亮。
3.3V → LED → 限流电阻 → GPIO 低电平 → GND
因此,在这块板上:
GPIO_ResetBits(GPIOF, GPIO_Pin_6); /* PF6 输出低电平,红灯亮 */
GPIO_SetBits(GPIOF, GPIO_Pin_6); /* PF6 输出高电平,红灯灭 */
LED 是高电平亮还是低电平亮,必须以实际原理图为准。 本地 V2 原理图还显示 RGB 灯公共端通过 J73 接到 3.3V;如果使用该版本,需要确认跳帽已接通。
三、GPIO 时钟和系统时钟是什么关系?
GPIO 是 STM32 内部的外设模块,需要时钟才能工作。STM32F407 的 GPIOF 位于 AHB1 总线,所以使用 GPIOF 前,要开启它的外设时钟:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
这行代码的作用是打开通往 GPIOF 的时钟开关。PF6、PF7、PF8 都属于 GPIOF,开启一次即可。
本工程的时钟配置以 25MHz 外部晶振为基础,经过 PLL 后得到 168MHz 系统时钟:
25MHz ÷ 25 × 336 ÷ 2 = 168MHz
时钟路径可以简化为:
外部晶振 → PLL → 系统时钟 → AHB1 总线 → GPIOF
GPIOF 有时钟,不代表 LED 会按这个频率闪烁。程序把 PF6 拉低后,它会保持低电平,红灯持续亮;只有程序再次改变 PF6 的电平,红灯状态才会改变。
四、GPIO 如何配置?
本实验把 PF6、PF7、PF8 配置成普通 GPIO 推挽输出:
| 配置项 | 设置 |
|---|---|
| 引脚 | PF6、PF7、PF8 |
| 模式 | 普通 GPIO 输出 |
| 输出类型 | 推挽输出 |
| 上下拉 | 无上下拉 |
| 输出速度 | GPIO_Speed_2MHz |
| 初始输出 | 高电平,三个颜色都熄灭 |
GPIO_Speed_2MHz 是 GPIO 的输出速度等级,不是流水灯每秒切换 200 万次。流水灯的切换速度由程序中的延时时间决定。
初始化时先预置高电平,再配置为输出模式,可以避免启用输出时 LED 短暂亮起。
五、LED BSP:封装引脚和电平操作
1. led/bsp_led.h
#ifndef BSP_LED_H
#define BSP_LED_H
#include "stm32f4xx.h"
/* 共阳 RGB LED:PF6 红、PF7 绿、PF8 蓝,低电平亮 */
#define LED_RED GPIO_Pin_6
#define LED_GREEN GPIO_Pin_7
#define LED_BLUE GPIO_Pin_8
#define LED_ALL (LED_RED | LED_GREEN | LED_BLUE)
void LED_GPIO_Config(void);
void LED_On(uint16_t pins);
void LED_Off(uint16_t pins);
#endif
2. led/bsp_led.c
#include "bsp_led.h"
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef gpio;
/* 开启 GPIOF 外设时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
/* 先输出高电平,让三个颜色保持熄灭 */
GPIO_SetBits(GPIOF, LED_ALL);
/* 配置 PF6、PF7、PF8 为普通 GPIO 推挽输出 */
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = LED_ALL;
gpio.GPIO_Mode = GPIO_Mode_OUT;
gpio.GPIO_OType = GPIO_OType_PP;
gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOF, &gpio);
}
void LED_On(uint16_t pins)
{
/* 低电平点亮 */
GPIO_ResetBits(GPIOF, pins & LED_ALL);
}
void LED_Off(uint16_t pins)
{
/* 高电平熄灭 */
GPIO_SetBits(GPIOF, pins & LED_ALL);
}
这样,main.c 只需写 LED_On(LED_RED),不必每次都考虑 PF6 对应哪个颜色、该输出高电平还是低电平。
LED_On() 只操作指定通道,不会自动关闭其他颜色。要保证红、绿、蓝轮流单独点亮,需要在打开下一个颜色前关闭上一个颜色。
六、SysTick 如何实现 1ms 计时?
使用空循环延时:
for (i = 0; i < 100000; i++)
{
}
很难准确知道经过了多久,因为耗时会受时钟频率和编译优化影响。本实验使用 Cortex-M4 内核的 SysTick 定时器。
假设内核时钟为 168MHz,下面的配置表示每计数 168000 个内核时钟周期,产生一次 SysTick 中断:
SysTick_Config(168000);
中断间隔为:
168000 ÷ 168000000 秒 = 0.001 秒 = 1ms
工程中使用下面的写法,根据 SystemCoreClock 计算计数值:
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000U);
SystemCoreClockUpdate() 读取当前时钟配置并更新软件变量,不会重新配置系统时钟。计算正确的前提是工程定义的外部晶振频率与实物一致。
1. delay/bsp_delay.h
#ifndef BSP_DELAY_H
#define BSP_DELAY_H
#include "stm32f4xx.h"
void Delay_Init(void);
void Delay_ms(uint32_t ms);
void Delay_Tick(void);
#endif
2. delay/bsp_delay.c
#include "bsp_delay.h"
/* 中断中会修改,主程序中会读取 */
static volatile uint32_t ticks;
void Delay_Init(void)
{
SystemCoreClockUpdate();
ticks = 0;
/* 配置 SysTick 约每 1ms 产生一次中断 */
if (SysTick_Config(SystemCoreClock / 1000U) != 0U)
{
while (1)
{
}
}
}
void Delay_Tick(void)
{
++ticks;
}
void Delay_ms(uint32_t ms)
{
uint32_t start = ticks;
while ((uint32_t)(ticks - start) < ms)
{
}
}
ticks 是毫秒计数。volatile 告诉编译器:这个变量可能被中断改变,主程序每次读取时都要获取它的当前值。
Delay_ms() 等待的是计数增加了多少,不是靠执行多少次空循环来估算时间。
3. 在 SysTick 中断函数中更新计数
在现有 User/stm32f4xx_it.c 中加入:
#include "bsp_delay.h"
找到原有的 SysTick_Handler(),将它修改为:
void SysTick_Handler(void)
{
Delay_Tick();
}
这里是修改已有函数,不要再写第二个同名的 SysTick_Handler()。
执行过程如下:
SysTick 每约 1ms 产生一次中断
↓
CPU 自动进入 SysTick_Handler()
↓
调用 Delay_Tick()
↓
ticks 加 1
以 Delay_ms(500) 为例,假设开始时 ticks 为 100:
当前 ticks | 与开始时相差 | 是否继续等待 |
|---|---|---|
| 100 | 0ms | 是 |
| 200 | 100ms | 是 |
| 599 | 499ms | 是 |
| 600 | 500ms | 否 |
如果 SysTick_Handler() 仍是空函数,ticks 就不会增加,Delay_ms(500) 也就无法结束。
这个延时函数会让主循环等待,但等待期间仍需要保持中断开启,因为 ticks 依赖 SysTick 中断更新。不要在 SysTick 中断函数内部调用这个 Delay_ms()。
七、main.c 负责流水灯顺序
#include "bsp_led.h"
#include "bsp_delay.h"
/* 每个颜色保持 500ms */
#define LED_STEP_MS 500U
int main(void)
{
LED_GPIO_Config();
Delay_Init();
while (1)
{
LED_On(LED_RED);
Delay_ms(LED_STEP_MS);
LED_Off(LED_RED);
LED_On(LED_GREEN);
Delay_ms(LED_STEP_MS);
LED_Off(LED_GREEN);
LED_On(LED_BLUE);
Delay_ms(LED_STEP_MS);
LED_Off(LED_BLUE);
}
}
程序运行顺序为:
开启 GPIOF 时钟、初始化 LED 引脚
↓
初始化 SysTick 毫秒计时
↓
红灯亮 500ms → 红灯灭
↓
绿灯亮 500ms → 绿灯灭
↓
蓝灯亮 500ms → 蓝灯灭
↓
返回红灯,持续循环
修改 LED_STEP_MS 就能改变流水速度。例如改为 200U,每种颜色约保持 200ms。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2501_92596338/article/details/166602534



