实现LED的呼吸效果,需要有节奏的变化LED的输入电压,由于单片机的输出是数字信号(要么高电平、要么低电平),常规情况下通过引脚控制LED时,LED灯只能处于亮/灭状态。为了实现呼吸灯效果,需要调节引脚输出方波的占空比(PWM),进而调节LED的输入电压,达到呼吸的效果。
我使用的开发板是野火STM32F103ZET6,LED灯的原理图如下所示:

LED3的负极接在PB5引脚,LED4的负极接在PB0引脚,LED5接在PB1引脚。
PB5引脚使用是TIM3的第二通道,PB0使用的是TIM3的第三通道,PB1使用的是TIM3的第四通道。
由于使用引脚产生PWM方波控制LED的亮暗,所以引脚需要设置为复用推挽输出。
在我使用的开发板中,LED3与PB5(TIM3_CH2)、LED4与PB0(TIM3_CH3)、LED5与PB1(TIM3_CH4)连接。但stm32引脚的默认复用功能设置中,PB5引脚默认没有复用TIM3_CH2,需要手动重定义后,才能复用TIM3_CH2。而LED4则默认复用了TIM3_CH3。基于以上原因,如果不手动映射,会发现近LED4有呼吸效果,LED3没有。


头文件(bsp_led.h)
#ifndef __BSP_LED_H
#define __BSP_LED_H
#include "stm32f10x.h"
/* 定义LED连接的GPIO端口, 用户只需要修改下面的代码即可改变控制的LED引脚 */
#define LED3_GPIO_PORT GPIOB /* LED3的GPIO端口 */
#define LED3_GPIO_PIN GPIO_Pin_5
#define LED4_GPIO_PORT GPIOB /* LED4的GPIO端口 */
#define LED4_GPIO_PIN GPIO_Pin_0
#define LED5_GPIO_PORT GPIOB /*LED5的GPIO端口 */
#define LED5_GPIO_PIN GPIO_Pin_1
/** the macro definition to trigger the led on or off
* 1 - off
* 0 - on
*/
#define ON 0
#define OFF 1
/* 使用标准的固件库控制IO*/
#define LED3(a) if (a) \
GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);\
else \
GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED4(a) if (a) \
GPIO_SetBits(LED4_GPIO_PORT,LED4_GPIO_PIN);\
else \
GPIO_ResetBits(LED4_GPIO_PORT,LED4_GPIO_PIN)
#define LED5(a) if (a) \
GPIO_SetBits(LED5_GPIO_PORT,LED5_GPIO_PIN);\
else \
GPIO_ResetBits(LED5_GPIO_PORT,LED5_GPIO_PIN)
/* 直接操作寄存器的方法控制IO */
#define digitalHi(p,i) {p->BSRR=i;} //输出为高电平
#define digitalLo(p,i) {p->BRR=i;} //输出低电平
#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态
/* 定义控制IO的宏 */
#define LED3_TOGGLE digitalToggle(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED4_TOGGLE digitalToggle(LED4_GPIO_PORT,LED4_GPIO_PIN)
#define LED5_TOGGLE digitalToggle(LED5_GPIO_PORT,LED5_GPIO_PIN)
/**
* @brief 初始化控制LED的IO,使用标准外设库函数
*/
void LED_GPIO_Config(void);
/**
* @brief 初始化控制LED的IO,使用直接操作寄存器的方法
*/
void LED_GPIO_Config_REG(void);
#endif源文件(bsp_led.c)
#include "bsp_led.h"
/**
* @brief 初始化控制LED的IO
* @param 无
* @retval 无
*/
void LED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB , ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED4_GPIO_PIN;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED4_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 通用推挽输出
GPIO_InitStructure.GPIO_Pin = LED5_GPIO_PIN;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED5_GPIO_PORT, &GPIO_InitStructure);
/* 关闭所有led灯 */
GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
GPIO_SetBits(LED4_GPIO_PORT, LED4_GPIO_PIN);
GPIO_SetBits(LED5_GPIO_PORT, LED5_GPIO_PIN);
}
void LED_GPIO_Config_REG(void)
{
/*开启LED相关的GPIO外设时钟*/
RCC->APB2ENR |= RCC_APB2Periph_GPIOB;
/*设置引脚模式为通用推挽输出,速率为50MHz */
LED3_GPIO_PORT->CRL |= GPIO_CRL_MODE5; // 输出模式11,最大速率50MHz
LED3_GPIO_PORT->CRL |= GPIO_CRL_CNF5_1; // 复用推挽输出10
LED3_GPIO_PORT->CRL &= ~ GPIO_CRL_CNF5_0; // 复用推挽输出10
LED4_GPIO_PORT->CRL |= GPIO_CRL_MODE0; // 输出模式11,最大速率50MHz
LED4_GPIO_PORT->CRL |= GPIO_CRL_CNF0_1; // 复用推挽输出10
LED4_GPIO_PORT->CRL &= ~ GPIO_CRL_CNF0_0; // 复用推挽输出10
LED5_GPIO_PORT->CRL |= GPIO_CRL_MODE1; // 输出模式11,最大速率50MHz
LED5_GPIO_PORT->CRL &= ~ (GPIO_CRL_CNF1_1 |GPIO_CRL_CNF1_0); // 推挽输出00
/* 关闭所有led灯 */
digitalHi(LED3_GPIO_PORT, LED3_GPIO_PIN);
digitalHi(LED4_GPIO_PORT, LED4_GPIO_PIN);
digitalHi(LED5_GPIO_PORT, LED5_GPIO_PIN);
}初始化TIM3
头文件(bsp_timer.h)
#ifndef __BSP_TIMER_H
#define __BSP_TIMER_H
#include "stm32f10x.h"
/**
* @brief 定时器配置函数,使用寄存器的方式
*/
void TIM3_Config(void); // 基础定时器3配置函数
void TIM3_Start(void); // 启动定时器
void TIM3_Stop(void); // 停止定时器
void TIM3_SetCH2DutyCycle(uint8_t duty); // 设置通道2占空比,参数范围0-100,对应0%-100%
void TIM3_SetCH3DutyCycle(uint8_t duty); // 设置通道3占空比,参数范围0-100,对应0%-100%
#endif源文件(bsp_timer.c)
#include "bsp_timer3.h"
void TIM3_Config(void)
{
// 1. 开启时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // 使能TIM3时钟
// 使能AFIO时钟,并进行TIM3部分重映射,使TIM3_CH2映射到PB5(如果不重定向,会发现LED3没有呼吸效果)
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // 使能 AFIO 时钟,在手册的2.1节的架构图中,AFIO在APB2总线上。
AFIO->MAPR &= ~AFIO_MAPR_TIM3_REMAP; // 保险起见,先将TIM3_REMAP清零,以防第0位被设置为1,导致完全重映射。
AFIO->MAPR |= AFIO_MAPR_TIM3_REMAP_PARTIALREMAP; // TIM3 部分重映射,在手册的8.4.2节中有说明,部分重映射后TIM3_CH2映射到PB5
// 2. 配置GPIO的工作模式、速度(如果在GPIO初始化时,已经设置,这里不用重复设置)
// 3. 配置定时器的工作模式、计数频率、自动重装载值等
// 3.1 定时器预分频器,72MHz时钟分频为10000Hz,每计数一次需要100us。
TIM3->PSC = 7200 - 1;
// 3.2 自动重装载值,99。每隔10ms(100us * 100 = 10000us = 10ms)溢出一次。
// 这时PWM的周期,频率为 72000000 HZ/ (7200 * 100) = 100Hz。缩放预分频*自动重装载值,得到PWM频率。
TIM3->ARR = 99;
// 3.3 配置定时器的工作模式,向上计数模式,边沿对齐模式。
TIM3->CR1 &= ~TIM_CR1_DIR; // 设置为0,向上计数
// 3.4 设置通道2的占空比,初始值为0,即TIM3->CCR2=0;(可选)
TIM3->CCR2 = 0;
// 3.5 捕获/输出选择,CC2S=00,输出模式
// 通道2
TIM3->CCMR1 &= ~TIM_CCMR1_CC2S;
// 通道3
TIM3->CCMR2 &= ~TIM_CCMR2_CC3S;
// 3.6 配置定时器的输出比较模式,PWM模式1,OC2M=110
// 通道2
TIM3->CCMR1 |= TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1; // 设置最高位和次高位为1
TIM3->CCMR1 &= ~TIM_CCMR1_OC2M_0; // 设置最低位为0
// 通道3
TIM3->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1; // 设置最高位和次高位为1
TIM3->CCMR2 &= ~TIM_CCMR2_OC3M_0; // 设置最低位为0
// 3.7 使能定时器的输出比较通道,CC2E=1,低电平有效 CC2P=1
// 通道2
TIM3->CCER |= TIM_CCER_CC2E; // 使能通道2的CC2输出
TIM3->CCER |= TIM_CCER_CC2P; // 低电平有效
// 通道3
TIM3->CCER |= TIM_CCER_CC3E; // 使能通道3的CC3输出
TIM3->CCER |= TIM_CCER_CC3P; // 低电平有效
}
void TIM3_Start(void)
{
TIM3->CR1 |= TIM_CR1_CEN; // 使能定时器
}
void TIM3_Stop(void)
{
TIM3->CR1 &= ~TIM_CR1_CEN; // 关闭定时器
}
void TIM3_SetCH2DutyCycle(uint8_t duty)
{
TIM3->CCR2 = duty;
}
void TIM3_SetCH3DutyCycle(uint8_t duty)
{
TIM3->CCR3 = duty;
}主函数(main.c)
#include "bsp_led.h"
#include "stm32f10x.h"
#include <stdio.h>
#include "bsp_timer3.h"
#include "delay.h"
int main(){
/* LED 端口初始化 */
LED_GPIO_Config_REG();
TIM3_Config(); // 配置定时器3
// 开启TIM3
TIM3_Start();
// 定义占空比、变化方向和步长
uint8_t duty = 0; // 初始占空比为0%
uint8_t direction = 1; // 变化方向,1表示增加,0表示减少
uint8_t step = 2; // 占空比变化的步长
while (1){
// 根据当前方向调整占空比
if (direction) {
duty += step; // 增加占空比
if (duty >= 100) { // 达到或超过100%时,切换方向
duty = 100; // 确保占空比不超过100%
direction = 0; // 切换为减少方向
}
} else {
duty -= step; // 减少占空比
if (duty <= 0) { // 达到或低于0%时,切换方向
duty = 0; // 确保占空比不低于0%
direction = 1; // 切换为增加方向
}
}
TIM3_SetCH2DutyCycle(duty); // 设置通道2占空比
TIM3_SetCH3DutyCycle(100-duty); // 设置通道3占空比
Delay_ms(20); // 延时20ms,控制占空比变化的速度
}
}
调节步长step、Delay_ms的毫秒数可以控制呼吸灯的缓慢效果。
