【STM32F407】Note_02 STM32实现指定频率呼吸灯

1. 引言

作为嵌入式入门新手,一般最开始的接触程序是如何点亮一个LED。而对于一些人来说,只需要单片机实现简单的控制功能,例如本人学习期间需要控制两个引脚交替输出。当时在网上下载了一个demo,但是这个程序定时部分很粗糙(就是烂大街的Delay函数),远远达不到个人预期,并且网上的呼吸灯程序千篇一律,并没有一个合适的指定频率的呼吸灯。虽然过去几年了,但是还是想写一篇很入门的文章,给那些也曾经有我这样的遭遇的人。

注:本文关于定时器部分只是采用了官方提供的API,并未涉及复杂寄存器配置,只针对GPIO管脚进行相关初始化,并且针对难以理解的地方给出详细的解释,并在文章末尾给出源码,代码简单易懂、易于修改,适合初学者阅读。

省流指示:本文使用官方定时器API实现了一个易于修改指定频率的呼吸灯。


2. 呼吸灯

2.1 环境搭建

关于环境搭建,请参考我的一篇文章【STM32F407】Note_01 STM32 编程环境搭建 -- Keil与VS code组合,个人比较喜欢 Keil + VS code组合开发项目。以下仅作为参考:

2.2 LED控制

本人所使用的是野火STM32F407开发板,因此需要找到该开发板电路原(《野火STM32F407_霸天虎V2开发板原理图20220219》)理图中LED相关电路。

可以看到,图中提供了4个LED,但是实际上只有两个LED(代号分别为D48和D6),其中下面三个LED合成一个RGB LED(D6)。四个LED右边连接PC3、PF6、PF7、PF8(即GPIOC8、GPIOF6、GPIOF7、GPIOF8)四个引脚,左侧连接3.3V电源。因此,如果我们需要是D48 LED量亮,则引脚PC3得输出低电平。

应注意到:如果要使用D6 LED,应使用短路帽插在J73上,是1、2两端导通。

有了以上理解,我们编程需要做的就是对我们所需要的引脚进行初始化即可。以D6 LED为例,若要使用这三个,则其初始化函数如下:

c 复制代码
void LED_GPIO_Config(void)
{
    //============================= 定义一个GPIO_InitTypeDef类型的结构体
    GPIO_InitTypeDef GPIO_InitStructure;

    //============================= 开启总线时钟,因为LED连接的管脚挂载在AHB1总线上,因此开启AHB1总线时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);

    //============================= 引脚设置
    GPIO_InitStructure.GPIO_Pin   = GreenLED_PIN;    // 选择需要控制的引脚
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;   // 设置引脚模式为输出模式
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   // 设置引脚输出类型为推挽输出
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;    // 设置引脚为上拉模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // 设置引脚速率为2MHz,不是越大越好,具体情况选择不同的速率,速率越大噪声越大

    GPIO_Init(GreenLED_GPIO_PORT, &GPIO_InitStructure); // 初始化GPIO引脚结构体

    // 初始化其他LED只需要修改响应的管脚和对应的PORT即可
    // GPIO_InitStructure.GPIO_Pin = RedLED_PIN;
    // GPIO_Init(RedLED_GPIO_PORT, &GPIO_InitStructure);

    // GPIO_InitStructure.GPIO_Pin = BlueLED_PIN;
    // GPIO_Init(BlueLED_GPIO_PORT, &GPIO_InitStructure);

    GreenLED_OFF; // 上电默认关闭呼吸灯
}

配置要点:

  1. 开启相应引脚的时钟。因为引脚都是挂载在总线上,因此需要开启总线时钟。
  2. 定义通用输入输出接口结构体 GPIO_InitTypeDef
  3. 配置结构体的引脚号 、模式 (输入或输出,因为我们需要引脚输出低电平,所以使用输出模式)、输出类型(不同的输出类型会影响引脚不同状态是默认上拉还是默认下来)、上下拉(下拉、既不上拉也不下拉、上拉,上拉:推挽模式下,上拉指的是GPIO引脚默认输出高电平)、速度(一般设置2MHz,但是视具体情况,速度越高噪声相应越大)
  4. 最后初始化结构体

2.3 频率控制

2.3.1 定时器初始化

这里需要使用两个关键APISystemInit和SysTick_Config。

c 复制代码
void SysClock_Init(void)
{
    SystemInit();                               // 初始化系统时钟,默认为168MHz
    if (SysTick_Config(SystemCoreClock / 1000)) // 定时周期1ms
    {
        while (1) // 设置失败陷入循环
        {
            /* code */
        }
    }
}

SystemInit初始化系统时钟,并将时钟频率设为169MHz,SysTick_Config(uint32_t ticks)为系统嘀嗒定时器配置。SysTick_Config形参ticks表示系统嘀嗒计数器从设置的ticks开始递减,当递减到0的时候产生中断。因为前面已经将系统设置为168MHz,即每秒钟系统执行168M个时钟,则1毫秒执行168M/1000个时钟。因此,我可通过系统嘀嗒计时器计数168M/1000个时钟即可产生一次中断,即1毫秒产生一次中断。注意宏定义SystemCoreClock的值为168000000,即对应168MHz。

同时我们只需要另一个计时器,每次产生中断,对计时器更新即可。系统嘀嗒定时器的中断函数名是固定的(这个主要跟它的启动文件startup_stm32f40_41xxx.s有关,不可随意更改,但是可以通过中断函数调用我们自己写得函数即可),他在stm32f4xx_it.c中的void SysTick_Handler(void)。

对于很多初学者来说,最大的困惑是系统怎么跳转到这个中断函数SysTick_Handler的。目前,对于刚入门的盆友来说,只需要知道,系统产生中断,系统会通过中断向量表自动跳转到该中断函数SysTick_Handler,然后执行中断函数,后面当你慢慢深入了解了中断系统。

2.3.2 周期设置

通过设定不同的周期,实现指定频率的呼吸灯。上一小节我们设置了一个计数器,没产生一个中断就计数+1,其物理意义也是当前已经经过了几个ms。当我们设置指定周期之后,当计数器对周期进行取余,余数为0,即可对LED进行反反转。

c 复制代码
void Ticker_Handle(void)
{
    if (isStartTick)
    {
        Ticker = (Ticker++) % 0xFFFF;
        if (Ticker % Period == 0)
        {
            GreenLED_TOGGLE;
        }
    }
}

2.4 详细代码

BreathingLED.h

c 复制代码
#ifndef _BREATHINGLED_H_
#define _BREATHINGLED_H_

#include "stm32f4xx.h"

// 引脚定义
/*******************************************************/
// R 红色灯
#define RedLED_PIN       GPIO_Pin_6
#define RedLED_GPIO_PORT GPIOF
#define RedLED_GPIO_CLK  RCC_AHB1Periph_GPIOF
#define RedLED_ON        GPIO_SetBits(RedLED_GPIO_PORT, RedLED_PIN)
#define RedLED_OFF       GPIO_ResetBits(RedLED_GPIO_PORT, RedLED_PIN)
#define RedLED_TOGGLE    GPIO_ToggleBits(RedLED_GPIO_PORT, RedLED_PIN)

// G 绿色灯
#define GreenLED_PIN       GPIO_Pin_7
#define GreenLED_GPIO_PORT GPIOF
#define GreenLED_GPIO_CLK  RCC_AHB1Periph_GPIOF
#define GreenLED_ON        GPIO_ResetBits(GreenLED_GPIO_PORT, GreenLED_PIN)
#define GreenLED_OFF       GPIO_SetBits(GreenLED_GPIO_PORT, GreenLED_PIN)
#define GreenLED_TOGGLE    GPIO_ToggleBits(GreenLED_GPIO_PORT, GreenLED_PIN)

// B 蓝色灯
#define BlueLED_PIN       GPIO_Pin_8
#define BlueLED_GPIO_PORT GPIOF
#define BlueLED_GPIO_CLK  RCC_AHB1Periph_GPIOF
#define BlueLED_ON        GPIO_ResetBits(BlueLED_GPIO_PORT, BlueLED_PIN)
#define BlueLED_OFF       GPIO_SetBits(BlueLED_GPIO_PORT, BlueLED_PIN)
#define BlueLED_TOGGLE    GPIO_ToggleBits(BlueLED_GPIO_PORT, BlueLED_PIN)
/************************************************************/

extern u8 isStartTick; // 开始计数
extern u16 Ticker;      // 嘀嗒计数器
extern u16 Period;      // 周期

void Set_Freq(u16 t);
void BreathingLED_Init(void);
void LED_GPIO_Config(void);
void SysClock_Init(void);
void Ticker_Handle(void);

#endif // _BREATHINGLED_H_

BreathingLED.c

c 复制代码
#include "BreathingLED.h"

u8 isStartTick; // 开始计数
u16 Ticker;     // 嘀嗒计数器
u16 Period;     // 周期

/**
 * @brief  初始化控制LED的IO
 * @param  无
 * @retval 无
 */
void BreathingLED_Init(void)
{
    Ticker      = 0;
    Period      = 500; // 默认2Hz,周期500ms
    isStartTick = 0;

    SysClock_Init();
    LED_GPIO_Config();
}

/**
 * @brief LED管脚初始化
 *
 */
void LED_GPIO_Config(void)
{
    //============================= 定义一个GPIO_InitTypeDef类型的结构体
    GPIO_InitTypeDef GPIO_InitStructure;

    //============================= 开启总线时钟,因为LED连接的管脚挂载在AHB1总线上,因此开启AHB1总线时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);

    //============================= 引脚设置
    GPIO_InitStructure.GPIO_Pin   = GreenLED_PIN;    // 选择需要控制的引脚
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;   // 设置引脚模式为输出模式
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   // 设置引脚输出类型为推挽输出
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;    // 设置引脚为上拉模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; // 设置引脚速率为2MHz,不是越大越好,具体情况选择不同的速率,速率越大噪声越大

    GPIO_Init(GreenLED_GPIO_PORT, &GPIO_InitStructure); // 初始化GPIO引脚结构体

    // 初始化其他LED只需要修改响应的管脚和对应的PORT即可
    // GPIO_InitStructure.GPIO_Pin = RedLED_PIN;
    // GPIO_Init(RedLED_GPIO_PORT, &GPIO_InitStructure);

    // GPIO_InitStructure.GPIO_Pin = BlueLED_PIN;
    // GPIO_Init(BlueLED_GPIO_PORT, &GPIO_InitStructure);

    GreenLED_OFF; // 上电默认关闭呼吸灯
}

/**
 * @brief 系统时钟初始化
 *
 */
void SysClock_Init(void)
{
    SystemInit();                               // 初始化系统时钟,默认为168MHz
    if (SysTick_Config(SystemCoreClock / 1000)) // 定时周期1ms
    {
        while (1) // 设置失败陷入循环
        {
            /* code */
        }
    }
}
/**
 * @brief 设置呼吸灯频率
 *
 * @param t : 周期,单位ms,范围≥1ms
 *
 *
 * @retval None
 */
void Set_Freq(u16 t)
{
    Period      = t;
    isStartTick = 1;
}

void Ticker_Handle(void)
{
    if (isStartTick)
    {
        Ticker = (Ticker++) % 0xFFFF;
        if (Ticker % Period == 0)
        {
            GreenLED_TOGGLE;
        }
    }
}

stm32f4xx_it.c

c 复制代码
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_it.h"
#include "main.h"

/** @addtogroup Template_Project
 * @{
 */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************/
/*            Cortex-M4 Processor Exceptions Handlers                         */
/******************************************************************************/

/**
 * @brief  This function handles NMI exception.
 * @param  None
 * @retval None
 */
void NMI_Handler(void)
{
}

/**
 * @brief  This function handles Hard Fault exception.
 * @param  None
 * @retval None
 */
void HardFault_Handler(void)
{
    /* Go to infinite loop when Hard Fault exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles Memory Manage exception.
 * @param  None
 * @retval None
 */
void MemManage_Handler(void)
{
    /* Go to infinite loop when Memory Manage exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles Bus Fault exception.
 * @param  None
 * @retval None
 */
void BusFault_Handler(void)
{
    /* Go to infinite loop when Bus Fault exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles Usage Fault exception.
 * @param  None
 * @retval None
 */
void UsageFault_Handler(void)
{
    /* Go to infinite loop when Usage Fault exception occurs */
    while (1)
    {
    }
}

/**
 * @brief  This function handles SVCall exception.
 * @param  None
 * @retval None
 */
void SVC_Handler(void)
{
}

/**
 * @brief  This function handles Debug Monitor exception.
 * @param  None
 * @retval None
 */
void DebugMon_Handler(void)
{
}

/**
 * @brief  This function handles PendSVC exception.
 * @param  None
 * @retval None
 */
void PendSV_Handler(void)
{
}

/**
 * @brief  This function handles SysTick Handler.
 * @param  None
 * @retval None
 */
void SysTick_Handler(void)
{
    Ticker_Handle();
    // TimingDelay_Decrement();
}

main.c

c 复制代码
/**
 *
 * @name 呼吸灯
 *
 * @brief 实现指定频率的呼吸灯
 *
 * 其他:\
 *
 */
#include "main.h"

/**
 * @brief  主函数
 * @details 呼吸灯的实现主要采用嘀嗒定时器实现指定频率的呼吸
 * @param  无
 * @retval 无
 */
int main(void)
{
    u16 T = 500;         // 周期,单位ms, T ≥ 1ms
    BreathingLED_Init(); // 呼吸灯初始化
    Set_Freq(T);         // 这是周期500ms,即2Hz
    while (1)            // 进入循环,每次嘀嗒定时器请求中断,对呼吸灯进行反转
    {
    }
}

3 总结

  • 希望对刚入门的盆友有一点点帮助。
  • 本文写得比较粗糙,如有不对的地方,欢迎在评论区指正。
相关推荐
一直C15 小时前
【ARM嵌入式开发】LCD液晶屏显示原理、色彩模式与显存地址映射详解
lcd·嵌入式·arm·寄存器·cortex-a7
Hespethorn15 小时前
洪泛、路由、网络栈:LoRa 自组网三条路线的设计取舍与量化对比
物联网·嵌入式
用户05101225729618 小时前
RGA(二)——RK3588 的核拓扑与能力差异
linux·嵌入式
用户05101225729618 小时前
RGA(一)——基础知识
linux·嵌入式
用户05101225729618 小时前
RGA(三)——实宽高、虚宽高与对齐约束
linux·嵌入式
燕卫博19 小时前
SSD201 BSP 开发教程
嵌入式·bsp·ssd201
一直C19 小时前
【嵌入式 ARM 驱动开发】PWM 脉宽调制原理与 GT9147 电容触控屏驱动开发详解
嵌入式硬件·lcd·嵌入式·arm汇编·imx6ull·cortex-a7
haliu20 小时前
【FHE 同态加密】我们如何实现同态加密推理(十四):为什么 `RESULT=PASS` 不是判据(纯 C11 · 零依赖)
人工智能·嵌入式·c·fhe·推理引擎·c11·边缘推理·同态加密推理
jianqiang.xue1 天前
【CStackGUI 实战】深色主题终端:app.dark 默认深色 + 顶层命名样式表 + 运行时切换
单片机·嵌入式·cstackgui·c语言gui·可视化拖拽
追涨杀跌老能手1 天前
GD32H759 + RT-Thread 工控实战--第7篇 OSPI Flash
开源·嵌入式