STM32实现呼吸灯效果原理
在 PWM 实现呼吸灯的过程中,核心的 “比较” 发生在定时器内部的计数器(CNT)与捕获比较寄存器(CCR)之间,这是硬件自动完成的操作。结合你提供的呼吸灯代码,具体逻辑如下:
一、谁和谁比较?
比较双方:
- 计数器(CNT):定时器内部不断递增(或递减)的数值,范围是
0
到ARR
(自动重装载值,即htim2.Init.Period
)。 - 捕获比较寄存器(CCR):存储你通过代码设置的占空比数值(即
pwm_buffer
中的值),是一个固定值(直到下一次更新)。
- 计数器(CNT):定时器内部不断递增(或递减)的数值,范围是
比较过程:定时器工作时,CNT 会从 0 开始递增,每来一个时钟脉冲加 1,直到达到
ARR
后重置为 0(向上计数模式)。在这个过程中,硬件会实时比较 CNT 和 CCR 的值:- 当
CNT < CCR
时:PWM 输出 “有效电平”(由OCPolarity
定义,如高电平,LED 亮)。 - 当
CNT >= CCR
时:PWM 输出 “无效电平”(如低电平,LED 暗)。
- 当
二、如何通过比较实现呼吸灯?
pwm_buffer
数组,本质是一系列动态变化的 CCR 值。结合硬件比较逻辑,过程如下:
初始状态:
- 假设
pwm_buffer[0] = 0
(CCR=0)。 - 比较结果:
CNT
始终>= 0
→ 输出无效电平(LED 暗)。
- 假设
亮度上升阶段:
pwm_buffer
中的值逐渐增大(如从 0→50→100→...→ARR)。- CCR 随数组值增大,
CNT < CCR
的时间变长 → 有效电平持续时间增加 → LED 逐渐变亮。
亮度下降阶段:
pwm_buffer
中的值逐渐减小(如从 ARR→100→50→...→0)。- CCR 随数组值减小,
CNT < CCR
的时间变短 → 有效电平持续时间减少 → LED 逐渐变暗。
循环往复:当
pwm_buffer
数组遍历完成后,重新从第一个值开始,形成 “暗→亮→暗” 的循环,即呼吸效果。
三、举例说明(假设 ARR=100)
若当前 CCR=30(来自
pwm_buffer
):- CNT 从 0→29 时,
CNT < 30
→ 输出高电平(LED 亮,持续 30 个时钟周期)。 - CNT 从 30→100 时,
CNT >= 30
→ 输出低电平(LED 暗,持续 70 个时钟周期)。 - 占空比 = 30%,LED 较暗。
- CNT 从 0→29 时,
若当前 CCR=80:
- 亮的时间占 80%,暗的时间占 20% → LED 较亮。
四、总结
呼吸灯的核心是定时器硬件自动比较 “计数器(CNT)” 和 “捕获比较寄存器(CCR)”,通过生成动态变化的 CCR 值(pwm_buffer
),改变了 “亮 / 暗时间的比例”,最终实现了平滑的亮度变化。硬件比较是实时且自动的,代码更新 CCR 值即可控制呼吸效果。
五、代码
这个函数是STM32CubeMX自动生成的,用于初始化TIM2的基本配置
/* TIM2 init function */
// 这个函数是STM32CubeMX自动生成的,用于初始化TIM2的基本配置
void MX_TIM2_Init(void)
{/* USER CODE BEGIN TIM2_Init 0 */// 用户可以在初始化开始处添加代码/* USER CODE END TIM2_Init 0 */// 自动生成的局部变量定义TIM_ClockConfigTypeDef sClockSourceConfig = {0};TIM_MasterConfigTypeDef sMasterConfig = {0};TIM_OC_InitTypeDef sConfigOC = {0};/* USER CODE BEGIN TIM2_Init 1 */// 用户可以在初始化中间添加代码/* USER CODE END TIM2_Init 1 */// 自动生成的定时器基本配置htim2.Instance = TIM2;htim2.Init.Prescaler = 71; /* 72MHz/(71+1) = 1MHz计数频率 */htim2.Init.CounterMode = TIM_COUNTERMODE_UP;htim2.Init.Period = 999; /* 1MHz/(999+1) = 1kHz PWM频率 */htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;HAL_TIM_Base_Init(&htim2);sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);HAL_TIM_PWM_Init(&htim2);sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);sConfigOC.OCMode = TIM_OCMODE_PWM1;sConfigOC.Pulse = 0;sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3);/* USER CODE BEGIN TIM2_Init 2 */// 用户可以在初始化结束处添加代码/* USER CODE END TIM2_Init 2 */HAL_TIM_MspPostInit(&htim2);}
初始化tim2的底层硬件,打开tim2通道三和dma通道二。
// 这个函数是STM32CubeMX自动生成的,用于初始化TIM2的底层硬件
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{if(tim_baseHandle->Instance==TIM2){/* USER CODE BEGIN TIM2_MspInit 0 */// 用户可以在初始化开始处添加代码/* USER CODE END TIM2_MspInit 0 *//* TIM2 clock enable */// 自动生成的时钟使能代码__HAL_RCC_TIM2_CLK_ENABLE();/* TIM2 DMA Init */// 自动生成的DMA配置代码/* TIM2_UP Init: 使用更新事件触发DMA写CCR3 */hdma_tim2_ch3_up.Instance = DMA1_Channel2; /* F1映射:TIM2_UP -> DMA1_Channel2 */hdma_tim2_ch3_up.Init.Direction = DMA_MEMORY_TO_PERIPH;hdma_tim2_ch3_up.Init.PeriphInc = DMA_PINC_DISABLE;hdma_tim2_ch3_up.Init.MemInc = DMA_MINC_ENABLE;hdma_tim2_ch3_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_tim2_ch3_up.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_tim2_ch3_up.Init.Mode = DMA_CIRCULAR;hdma_tim2_ch3_up.Init.Priority = DMA_PRIORITY_LOW;HAL_DMA_Init(&hdma_tim2_ch3_up);/* 启动时钟 */__HAL_RCC_DMA1_CLK_ENABLE();// 改为更新事件DMA请求__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_UPDATE],hdma_tim2_ch3_up);/* TIM2 interrupt Init */// 自动生成的中断配置代码HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(TIM2_IRQn);/* USER CODE BEGIN TIM2_MspInit 1 */// 用户可以在初始化结束处添加代码/* USER CODE END TIM2_MspInit 1 */}
}
生成一个平滑的呼吸灯 PWM 占空比序列,通过将该序列按时间依次输出到 LED 的 PWM 通道,即可实现 LED 从暗→亮→暗的呼吸效果。
void MX_TIM2_PWM_Init(void)
{/* 动态生成平滑呼吸曲线:duty = sin^2(0..pi) * Period */float period = (float)htim2.Init.Period;float pi = 3.1415926f;for (int i = 0; i < PWM_BUFFER_SIZE; i++) {float phase = (pi * (float)i) / (float)(PWM_BUFFER_SIZE - 1);float s = sinf(phase);float duty = s * s * period;int val = (int)(duty + 0.5f);if (val < 0) val = 0;if (val > (int)period) val = (int)period;pwm_buffer[i] = (uint16_t)val;}
}
以下这段代码 Start_Breathing_LED(void)
是启动呼吸灯功能的核心函数,通过定时器 PWM + DMA的方式实现 LED 的自动呼吸效果,无需 CPU 频繁干预
void Start_Breathing_LED(void)
{HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);HAL_DMA_Start_IT(&hdma_tim2_ch3_up, (uint32_t)pwm_buffer, (uint32_t)&htim2.Instance->CCR3, PWM_BUFFER_SIZE);__HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_UPDATE);__HAL_TIM_ENABLE(&htim2);
}
以下是逐行代码的详细解释:
1. HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
- 功能:启动 TIM2 定时器的通道 3(CH3)的 PWM 输出功能。
- 细节:
&htim2
是 TIM2 的句柄(包含定时器的配置参数,如周期、预分频等)。TIM_CHANNEL_3
指定启用通道 3,该通道需提前配置为 PWM 模式(如TIM_OCMODE_PWM1
),且对应引脚已配置为复用输出(通常在HAL_TIM_MspPostInit
中完成)。- 调用后,TIM2_CH3 开始输出 PWM 波形,但此时占空比由
CCR3
寄存器的初始值决定,尚未进入呼吸模式。
2. HAL_DMA_Start_IT(&hdma_tim2_ch3_up, (uint32_t)pwm_buffer, (uint32_t)&htim2.Instance->CCR3, PWM_BUFFER_SIZE);
- 功能:启动 DMA 通道,并配置其从内存(
pwm_buffer
)向外设(CCR3
寄存器)传输数据,同时使能 DMA 中断。 - 参数解析:
&hdma_tim2_ch3_up
:TIM2_CH3 对应的 DMA 通道句柄(需提前在 CubeMX 中配置,如 STM32F103 中 TIM2_CH3 通常映射到 DMA1_Channel2)。(uint32_t)pwm_buffer
:源地址,即存储呼吸灯占空比序列的数组(你之前生成的sin²
曲线数据)。(uint32_t)&htim2.Instance->CCR3
:目标地址,即 TIM2 通道 3 的捕获比较寄存器(CCR3
),PWM 的占空比由该寄存器的值决定。PWM_BUFFER_SIZE
:传输次数,即需要从pwm_buffer
传输到CCR3
的数据总量(数组长度)。
- 作用:DMA 会自动将
pwm_buffer
中的值依次写入CCR3
,无需 CPU 手动调用__HAL_TIM_SET_COMPARE
,减少 CPU 负担。
3. __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_UPDATE);
- 功能:使能 TIM2 的 “更新事件(Update Event)” 触发 DMA 传输。
- 细节:
- 定时器的 “更新事件” 指计数器(CNT)从最大值(ARR)溢出并重置为 0 的时刻(向上计数模式)。
TIM_DMA_UPDATE
表示将 DMA 触发源绑定到 TIM2 的更新事件。- 使能后,每次 TIM2 发生更新事件(即一个 PWM 周期结束时),会自动触发 DMA 传输下一个占空比数据到
CCR3
。
4. __HAL_TIM_ENABLE(&htim2);
- 功能:启动 TIM2 定时器,使其开始计数。
- 细节:
- 定时器启动后,计数器(CNT)开始从 0 递增,达到 ARR 后触发更新事件,进而触发 DMA 传输下一个占空比。
- 结合前面的配置,此时整个流程会自动运行:
TIM2计数 → 溢出触发更新事件 → DMA传输下一个占空比到CCR3 → TIM2根据新的CCR3输出PWM → 循环直到数组传输完成
。
整体工作流程(呼吸灯实现逻辑)
- 启动 PWM:
HAL_TIM_PWM_Start
使 TIM2_CH3 开始输出 PWM,但初始占空比可能为 0。 - 配置 DMA:
HAL_DMA_Start_IT
让 DMA 准备好从pwm_buffer
向CCR3
传输数据,并允许 DMA 中断(方便传输完成后处理,如循环传输)。 - 绑定触发源:
__HAL_TIM_ENABLE_DMA
设定 “TIM2 更新事件” 作为 DMA 的触发信号,确保每个 PWM 周期更新一次占空比。 - 启动定时器:
__HAL_TIM_ENABLE
让 TIM2 开始工作,触发后续的计数、更新事件和 DMA 传输。
最终效果:pwm_buffer
中的占空比序列会被 DMA 自动、周期性地写入CCR3
,TIM2 根据实时的CCR3
值输出 PWM,使 LED 按sin²
曲线平滑亮暗变化,实现呼吸效果。
关键优势
- 无 CPU 干预:DMA 自动完成占空比更新,CPU 可空闲处理其他任务。
- 实时性强:每个 PWM 周期精准更新一次占空比,呼吸效果平滑无卡顿。
- 低功耗:减少 CPU 频繁中断或轮询,降低系统功耗。