2501_92596338头像
关注

STM32F407 :RGB 流水灯的原理与实现

一、实验目标

使用 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与开始时相差是否继续等待
1000ms是
200100ms是
599499ms是
600500ms否

如果 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

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--