【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 定时器初始化

这里需要使用两个关键APISystemInitSysTick_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 总结

  • 希望对刚入门的盆友有一点点帮助。
  • 本文写得比较粗糙,如有不对的地方,欢迎在评论区指正。
相关推荐
飞凌嵌入式2 小时前
飞凌嵌入式T113-i开发板RISC-V核的实时应用方案
人工智能·嵌入式硬件·嵌入式·risc-v·飞凌嵌入式
blessing。。3 小时前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
网易独家音乐人Mike Zhou19 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
jjyangyou2 天前
物联网核心安全系列——智能汽车安全防护的重要性
算法·嵌入式·产品经理·硬件·产品设计
FreakStudio3 天前
全网最适合入门的面向对象编程教程:59 Python并行与并发-并行与并发和线程与进程
python·单片机·嵌入式·面向对象·电子diy·电子计算机
憧憬一下4 天前
UART硬件介绍
arm开发·嵌入式硬件·串口·嵌入式·linux驱动开发
佳肴4 天前
BT04-E蓝牙模块
嵌入式
zxfeng~7 天前
AG32 FPGA部分简单开发
fpga开发·嵌入式·ag32
电子老师傅7 天前
如何挑选海外4G模组?这里有秘籍!
物联网·嵌入式·硬件工程·4g模组
CodeAllen嵌入式8 天前
嵌入式面试题练习 - 2024/11/15
数据结构·windows·嵌入式硬件·算法·嵌入式·嵌入式系统