1. 项目概述与核心价值
最近在调试一个基于STM32F103的小型电机驱动板,需要用到一个额外的PWM信号来控制一个风扇的转速。板子的主控资源已经分配得差不多了,翻看原理图发现,PB0这个引脚恰好是空闲的,而且它对应着定时器TIM3的通道3。这不巧了嘛,直接用TIM3_CH3来输出PWM波,既不用动硬件,又能完美解决问题。对于很多从标准库(StdPeriph Library)入门STM32的朋友来说,虽然现在HAL/LL库是趋势,但标准库的代码结构清晰、寄存器操作直观,依然是理解底层原理和进行快速原型开发的利器。这个项目标题“【STM32库函数】使用TIM3的CH3实现PB0口的PWM波输出”,就是一个非常经典且实用的切入点。
它要解决的核心问题很明确:如何通过STM32标准外设库,配置一个特定的定时器通道,使其在指定的GPIO引脚上输出脉宽调制信号。PWM可以说是单片机控制领域的“万金油”,从LED调光、电机调速、舵机控制到开关电源,无处不在。掌握如何精准地配置和输出PWM,是嵌入式开发的基本功。这个配置过程涉及GPIO复用、定时器时基单元、输出比较模式等多个模块的协同工作,对新手理解STM32外设的联动关系非常有帮助。
本文将带你完整走一遍流程,从工程环境搭建、原理分析、库函数调用到代码编写和调试。我会基于最常见的STM32F103C8T6(俗称“蓝桥杯”或“最小系统板”核心型号)进行讲解,但原理通用,你可以轻松移植到F1系列的其他型号。我会重点解释每个配置步骤“为什么”要这么做,并分享一些在调试PWM时容易踩坑的地方和实用技巧。
2. 硬件连接与原理分析
2.1 引脚复用关系解析
STM32的GPIO功能非常强大,一个物理引脚往往可以复用为多种外设功能,这需要通过GPIO的复用功能重映射寄存器来配置。对于我们的目标:使用TIM3的通道3(TIM3_CH3)从PB0输出PWM,首先必须查证这个连接关系是否成立。
查阅STM32F103xx的数据手册(Datasheet)或参考手册(Reference Manual),在引脚定义章节和定时器复用功能映射章节,我们可以确认:在默认情况下(即不复用重映射时),定时器TIM3的通道3(TIM3_CH3)对应的输出引脚就是PB0。这是一个固定的硬件连接,我们不需要进行任何重映射操作。这一点非常重要,它决定了我们配置的起点。
注意:并非所有定时器的所有通道都直接连接到某个GPIO。例如,TIM3_CH1默认是PA6,但可以通过部分重映射功能映射到PB4。对于TIM3_CH3和PB0这个组合,是默认连接,配置最为简单。
2.2 PWM生成的核心原理
PWM,全称脉冲宽度调制,其核心是用一个数字信号来模拟模拟量。一个PWM波由两个关键参数定义:频率(或周期)和占空比。
- 周期(Period):一个完整PWM波形(从高电平开始到下一个高电平开始)的时间长度。其倒数即为频率(Frequency)。频率决定了PWM控制的“细腻度”,比如控制LED时,频率太低会闪烁,控制电机时,频率需要避开机械共振点。
- 占空比(Duty Cycle):在一个周期内,高电平时间所占的百分比。占空比直接决定了输出信号的平均电压,是控制对象(如亮度、速度)的关键。
在STM32的定时器中,是如何硬件实现PWM的呢?这依赖于定时器的输出比较(Output Compare)功能。我们配置定时器以一个固定的频率(由预分频器PSC和自动重装载寄存器ARR决定)向上或向下计数。同时,我们设置一个捕获/比较寄存器(CCR)。定时器的计数器(CNT)会不断与CCR值进行比较。
当定时器工作在PWM模式1或模式2时:
- 计数器CNT开始计数。
- 当CNT < CCR时,根据模式设置,通道输出有效电平(通常为高电平)。
- 当CNT >= CCR且CNT <= ARR时,通道输出无效电平(通常为低电平)。
- 当CNT计数到ARR值(溢出)时,产生更新事件,CNT清零重新开始计数,输出电平也根据新的CCR值(如果已更新)进行判断,周而复始。
这样,通过改变ARR值,我们改变了PWM的周期;通过改变CCR值,我们改变了高电平的时间,从而改变了占空比。整个过程由硬件自动完成,不占用CPU资源,非常高效。
3. 开发环境准备与工程建立
3.1 工具链选择
对于标准库开发,我们通常需要以下几样东西:
- 集成开发环境(IDE):Keil MDK-ARM(µVision)或 IAR Embedded Workbench 是经典选择。社区版有代码大小限制,但对于学习和小项目足够。本文示例将基于Keil MDK。
- STM32标准外设库:从ST官网下载,例如
STM32F10x_StdPeriph_Lib。它包含了所有外设的驱动源码、头文件、启动文件和大量示例。 - 目标芯片支持包(Device Family Pack):在Keil中需要安装对应芯片系列(如STM32F1xx)的DFP,以便编译器识别芯片和进行调试。
- 调试器/编程器:ST-Link V2是最常见且性价比高的选择,也兼容J-Link等。
3.2 标准库工程搭建要点
如果你是从零新建一个标准库工程,步骤稍显繁琐但结构清晰:
- 创建项目文件夹:例如
Project,内部建立User(用户代码)、Libraries(库文件)、Output(输出文件)、Listings(列表文件)等子文件夹。 - 拷贝库文件:将标准库
Libraries目录下的CMSIS和STM32F10x_StdPeriph_Driver复制到你的工程Libraries文件夹。 - 拷贝启动文件:根据你的芯片Flash容量(如STM32F103C8T6是64KB,属于中等容量),从库的
Project/STM32F10x_StdPeriph_Template中找到对应的启动文件(如startup_stm32f10x_md.s,md代表Medium-density)放到User文件夹。 - 创建工程:在Keil中新建工程,选择你的目标设备(如 STM32F103C8)。
- 添加文件分组:在项目管理窗口中,创建分组如
User、CMSIS、FWLIB,并将对应的.c源文件添加进去。特别注意,FWLIB下的外设驱动源文件,通常只添加你用到的几个(如stm32f10x_gpio.c,stm32f10x_tim.c,stm32f10x_rcc.c),以减小代码体积。 - 配置魔术棒(Options for Target):
Target标签:勾选Use MicroLIB(简化版C库,节省空间)。C/C++标签:在Define框中定义全局宏,这是关键一步。对于STM32F103系列,通常需要USE_STDPERIPH_DRIVER(告诉编译器使用标准库)和STM32F10X_MD(对应中等容量芯片)。在Include Paths中添加所有头文件所在路径。Debug标签:选择你的调试器(如ST-Link),并勾选Run to main()。Utilities标签:设置调试器的下载算法,通常选STM32F10x Medium-density Flash。
这个过程对于初学者可能是个挑战,但理解一次后受益无穷。网上也有很多现成的标准库工程模板,可以拿来作为起点进行修改,能节省大量时间。
4. 核心代码实现与分步详解
下面我们进入核心环节,一步步编写代码,实现TIM3_CH3在PB0上输出PWM。我们假设目标PWM频率为1kHz,初始占空比为50%。
4.1 GPIO初始化配置
PB0需要被配置为复用推挽输出模式,因为定时器的PWM信号是由外设(TIM3)产生的,GPIO只是作为输出引脚。
/** * @brief 初始化PB0为TIM3_CH3的复用功能输出 * @param 无 * @retval 无 */ void TIM3_CH3_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO初始化结构体 /* 第1步:开启GPIOB的时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); /* 第2步:配置GPIOB Pin0 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 引脚0 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB }代码解析与注意事项:
- 开启时钟:任何外设使用前,必须先开启其时钟。GPIOB挂载在APB2总线上,所以使用
RCC_APB2PeriphClockCmd。 - 模式选择:
GPIO_Mode_AF_PP(复用推挽输出)是用于外设信号输出的标准模式。推挽输出提供较强的驱动能力,高低电平都很稳定。 - 速度选择:
GPIO_Speed_50MHz指的是IO口的最大响应速度。对于1kHz的PWM,这个速度绰绰有余。但在输出更高频率的PWM(比如上百kHz)时,选择更高的速度(如50MHz)可以减少信号边沿的失真。
4.2 定时器时基单元初始化
这是配置PWM频率(周期)的关键步骤。
/** * @brief 配置TIM3的时基单元,决定PWM频率 * @param 无 * @retval 无 */ void TIM3_Base_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定义时基初始化结构体 /* 第1步:开启TIM3的时钟 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* 第2步:配置时基单元参数 */ // 假设系统时钟(SYSCLK)为72MHz,APB1预分频器为2,所以TIM3的时钟为72MHz // 目标PWM频率为1kHz (1000Hz) // 定时器计数频率 = TIM_CLK / (PSC + 1) // PWM频率 = 定时器计数频率 / (ARR + 1) // 我们令 ARR = 7199, PSC = 0 // 则:计数频率 = 72MHz / (0+1) = 72MHz // PWM频率 = 72MHz / (7199+1) = 72MHz / 7200 = 10,000Hz = 10kHz?等等,这里计算有误! // 重新计算:若 ARR=7199, 则周期 T = (7199+1) / 72MHz = 7200 / 72e6 = 0.0001s = 0.1ms // 频率 F = 1/T = 1/0.0001 = 10,000Hz = 10kHz。这不是我们想要的1kHz。 // 正确计算1kHz: // Fpwm = TIM_CLK / ((PSC+1) * (ARR+1)) // 72,000,000 / ((PSC+1)*(ARR+1)) = 1000 // (PSC+1)*(ARR+1) = 72000 // 为了得到较高的计数分辨率(便于精细调节占空比),我们让ARR尽可能大。 // 令 ARR = 7199, 则 (PSC+1) = 72000 / (7199+1) = 72000 / 7200 = 10 // 所以 PSC = 9 // 验证:Fpwm = 72M / (10 * 7200) = 72,000,000 / 72,000 = 1000Hz。正确。 TIM_TimeBaseStructure.TIM_Period = 7199; // 自动重装载值 ARR TIM_TimeBaseStructure.TIM_Prescaler = 9; // 预分频系数 PSC TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频,与采样相关,通常不分 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 初始化TIM3时基单元 }参数计算心得: 计算PSC和ARR是配置PWM的第一步,也是容易出错的一步。核心公式一定要记牢:PWM频率 = 定时器输入时钟频率 / ((PSC + 1) * (ARR + 1))。
- 定时器输入时钟频率:需要根据你的系统时钟和总线分频情况确定。对于STM32F103,如果系统时钟是72MHz,TIM2-TIM7挂在APB1上。如果APB1预分频系数是1,则定时器时钟为72MHz;如果APB1预分频系数是N(≠1),则定时器时钟是APB1时钟的2倍。这是STM32时钟树的一个特点,务必查证。
- ARR值的选择:ARR决定了计数器的最大值,也间接决定了占空比调节的分辨率。ARR越大,CCR可以设置的值范围就越大(0-ARR),占空比调节就越精细。例如ARR=7199时,占空比分辨率是1/7200≈0.0139%。所以,在满足频率要求的前提下,尽量让ARR大一些。
- PSC值的作用:PSC用于对定时器时钟进行第一次分频,以得到合适的计数频率。当所需PWM频率很低时,即使ARR取到最大值65535(对于16位定时器)也可能不够,这时就需要增大PSC来降低计数频率。
4.3 PWM输出通道初始化
这是配置通道工作模式、极性、输出使能的关键。
/** * @brief 配置TIM3的通道3为PWM输出模式 * @param 无 * @retval 无 */ void TIM3_CH3_PWM_Config(void) { TIM_OCInitTypeDef TIM_OCInitStructure; // 定义输出比较初始化结构体 /* 第1步:配置PWM模式 */ // 初始化结构体,通常先赋一个默认值,避免随机值影响 TIM_OCStructInit(&TIM_OCInitStructure); // 配置为PWM模式1 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 设置输出比较极性:当定时器计数小于CCR时,输出有效电平 // TIM_OCPolarity_High 表示有效电平为高电平 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出使能 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 初始的脉冲宽度(占空比)设置,CCR值 // 占空比 = CCR / (ARR+1)。ARR=7199, 50%占空比对应 CCR = 3600 TIM_OCInitStructure.TIM_Pulse = 3600; // 初始化通道3 TIM_OC3Init(TIM3, &TIM_OCInitStructure); // 这个函数很关键!它使能通道3的预装载寄存器,这样我们修改CCR值时, // 不会立即生效,而是在下一次更新事件(计数器溢出)时才生效,防止了PWM波形中出现毛刺。 TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); }模式与极性详解:
- TIM_OCMode_PWM1 vs PWM2:这是两个互补的模式。
- PWM模式1:在向上计数时,当CNT < CCR,通道输出有效电平(由极性定义);当CNT > CCR,输出无效电平。
- PWM模式2:逻辑相反,当CNT < CCR时输出无效电平。 大多数情况下使用PWM模式1,配合
TIM_OCPolarity_High(有效电平为高),就是我们最常见的“高电平有效”PWM波。
- 预装载功能(Preload):
TIM_OCxPreloadConfig这个函数调用至关重要。它开启了对应通道捕获/比较寄存器(CCRx)的预装载功能。这意味着,你通过TIM_SetComparex()函数修改的CCR值,会先写入一个“影子寄存器”,等到下一次定时器更新事件(UEV)发生时,这个值才会被真正加载到当前工作的CCR寄存器中。这确保了在一个完整的PWM周期内,占空比参数是同步更新的,避免了在周期中间改变占空比可能导致的脉冲宽度异常(比如产生一个极窄或极宽的脉冲),保证了波形的稳定和纯净。
4.4 主函数整合与使能
将以上模块整合,并在主函数中调用。
#include "stm32f10x.h" // 函数声明 void TIM3_CH3_GPIO_Config(void); void TIM3_Base_Config(void); void TIM3_CH3_PWM_Config(void); int main(void) { // 1. 初始化GPIO TIM3_CH3_GPIO_Config(); // 2. 初始化定时器时基 TIM3_Base_Config(); // 3. 初始化PWM输出通道 TIM3_CH3_PWM_Config(); // 4. 使能定时器!这一步千万别忘了,否则计数器不工作。 TIM_Cmd(TIM3, ENABLE); // 5. 使能定时器的主输出(对于高级定时器TIM1/TIM8必须,对于通用定时器TIM3,此函数无实际作用,但写上是个好习惯) // TIM_CtrlPWMOutputs(TIM3, ENABLE); // 通用定时器无此功能,注释掉或删除 while (1) { // 主循环,这里可以添加动态修改占空比的代码 // 例如:通过按键或传感器改变 TIM_SetCompare3(TIM3, new_CCR_Value); } }关键点提醒:
- 执行顺序:初始化顺序一般是 GPIO -> 定时器时基 -> PWM通道。确保时钟先开启。
- 遗忘的使能:
TIM_Cmd(TIM3, ENABLE);这行代码是让定时器计数器开始运行的核心。很多初学者配置了一堆参数,最后发现没有输出,问题往往就出在忘了使能定时器。 - 高级定时器的区别:如果你使用的是高级定时器(如TIM1, TIM8),在输出PWM前,还需要调用
TIM_CtrlPWMOutputs(TIMx, ENABLE);来使能主输出。但对于我们使用的通用定时器TIM3,这个函数是无效的,库函数内部会忽略它。知道这个区别可以避免困惑。
5. 调试技巧与波形观测
代码写完了,下载到板子里,怎么验证PWM是否正常输出了呢?
5.1 软件调试方法
使用调试器(Debugger):
- 在Keil中进入调试模式,打开逻辑分析仪(Logic Analyzer)或系统查看器(System Viewer)。
- 添加要观察的信号,比如
GPIOB_IDR.0(PB0的输入数据寄存器)或者直接添加外设信号TIM3->CCR3。虽然不能直接看到模拟波形,但可以通过数值变化判断定时器是否在工作,CCR值是否被加载。 - 单步执行代码,检查各个初始化函数执行后,对应的寄存器(如TIM3->CR1, TIM3->CCMR2)是否被正确配置。
变量监测:在Watch窗口添加
TIM3->CNT和TIM3->CCR3,观察它们的值是否在规律变化(CNT循环递增,CCR3保持不变或按你代码变化),这能间接证明定时器在运行且PWM比较逻辑已启用。
5.2 硬件测量方法(最直接)
这是最推荐、最直观的方法。
- 工具准备:一台数字示波器或逻辑分析仪。对于1kHz的PWM,普通的示波器完全够用。
- 连接测量:将示波器探头的地线夹在板子的GND上,探头尖端接触PB0引脚(或者连接到PB0的电路节点)。
- 观测波形:
- 时基(Timebase):调整示波器水平时基到大约 1ms/div 或 500µs/div,以便能看到多个完整周期。
- 触发(Trigger):设置为边沿触发,通道选择测量通道,触发沿为上升沿。
- 观测结果:你应该能看到一个稳定的方波。测量其周期应为1ms(对应1kHz),高电平时间应为0.5ms(对应50%占空比)。使用示波器的自动测量功能(Period, Freq, Duty)可以快速读取这些参数。
- 动态测试:在
while(1)循环中添加一段代码,每隔一段时间改变TIM_SetCompare3(TIM3, new_value);的值,然后在示波器上观察波形的占空比是否平滑变化。这是验证PWM控制功能是否灵活的好方法。
5.3 无仪器情况下的简易判断
如果没有示波器,也有“土办法”:
- 接LED:将一个LED(串联一个220Ω-1kΩ的限流电阻)接到PB0和GND之间。如果PWM输出正常且频率较低(比如几十Hz),你会看到LED闪烁。如果频率较高(超过100Hz),由于人眼的视觉暂留,你会看到LED呈现不同的亮度。改变CCR值,LED亮度应该随之变化。注意:这种方法只能定性判断PWM有无输出和占空比是否变化,无法得知精确频率和波形质量。
- 万用表测电压:使用数字万用表的直流电压档测量PB0引脚对地的电压。如果PWM正常输出,万用表显示的是一个平均电压值。对于3.3V系统,50%占空比时,平均电压应该在1.65V左右。改变占空比,这个平均电压值会线性变化。这也是一种间接的验证方法。
6. 常见问题排查与解决方案
在实际操作中,你可能会遇到以下问题,这里我整理了排查思路:
6.1 问题一:完全没有波形输出,引脚一直是低电平或高电平。
- 检查步骤:
- 时钟是否开启?:这是最常见的原因。双重检查
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);和RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);是否被正确调用。 - GPIO模式是否正确?:必须配置为
GPIO_Mode_AF_PP(复用推挽输出)。如果错配为GPIO_Mode_Out_PP(通用推挽输出),定时器无法控制该引脚。 - 定时器使能了吗?:确认
TIM_Cmd(TIM3, ENABLE);被执行。 - 引脚复用映射对吗?:确认你使用的芯片,TIM3_CH3默认就是PB0,没有启用重映射。如果原理图或芯片手册有出入,需要检查
GPIO_PinRemapConfig相关配置。 - 硬件连接:用万用表蜂鸣档检查PB0引脚是否确实连接到了你的测量点或负载上,有没有虚焊或短路到地/电源。
- 时钟是否开启?:这是最常见的原因。双重检查
6.2 问题二:有波形输出,但频率或占空比不对。
- 检查步骤:
- 计算错误:重新核算PSC和ARR的值。使用公式
Fpwm = Fclk / ((PSC+1)*(ARR+1))。务必确认Fclk(TIM3的输入时钟频率)是正确的。参考你的SystemInit()函数或时钟配置代码,确认系统时钟和APB1总线时钟。 - 寄存器配置被覆盖:有时在多个地方初始化了同一个定时器,或者后续代码意外修改了TIM3的寄存器。可以在调试模式下,在初始化完成后和主循环中,查看TIM3->PSC, TIM3->ARR, TIM3->CCR3等关键寄存器的值是否与你设定的一致。
- 测量方法问题:示波器探头衰减比是否设置正确(如1:1或10:1)?时基档位是否合适?尝试使用示波器的自动测量功能获取频率和占空比。
- 计算错误:重新核算PSC和ARR的值。使用公式
6.3 问题三:波形有毛刺或抖动。
- 检查步骤:
- 预装载寄存器未使能:这是产生毛刺的主要原因之一。确保调用了
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);。这样修改CCR值会在更新事件时同步生效,波形干净。 - 软件修改CCR的时机:如果你在程序中动态修改CCR,尽量避免在计数器运行到接近CCR值或ARR值时进行修改。虽然开启了预装载,但极端情况下仍可能有问题。可以在计数器为0(或一个较小值)时修改,或者使用定时器的更新中断,在中断服务程序里修改。
- 电源噪声:为MCU供电的电源是否干净?可以尝试在MCU的VDD和GND之间并联一个0.1µF和10µF的电容进行滤波。
- 负载影响:如果PB0引脚驱动的负载电流较大(如直接驱动电机),可能会引起电压波动。建议使用三极管、MOS管或电机驱动芯片进行隔离驱动。
- 预装载寄存器未使能:这是产生毛刺的主要原因之一。确保调用了
6.4 问题四:想动态改变频率怎么办?
通过上面的分析我们知道,PWM频率由Fclk、PSC和ARR共同决定。Fclk通常系统运行期间不变。所以动态调频主要就是动态修改PSC或ARR。
- 修改ARR:这是最常用的方法,因为ARR直接决定了周期。使用
TIM_SetAutoreload(TIM3, new_ARR_value);。注意:修改ARR可能会影响当前周期的完整性,最好在计数器CNT为0时修改,或者使用更新事件中断来同步修改。 - 修改PSC:使用
TIM_PrescalerConfig(TIM3, new_PSC_value, TIM_PSCReloadMode_Immediate);。预分频器有缓冲寄存器,可以设置立即更新或下次更新时生效。改变PSC会立刻改变计数频率,可能导致当前周期时间异常,需谨慎处理。 - 最佳实践:对于需要平滑、无毛刺地改变频率,通常的步骤是:
- 关闭定时器输出或进入刹车状态(如果需要)。
- 同时更新PSC和ARR(先设置新值到预装载寄存器)。
- 产生一个更新事件(软件触发
TIM_GenerateEvent(TIM3, TIM_EventSource_Update);),让所有新参数同时生效。 - 重新使能输出。
7. 项目扩展与进阶应用
掌握了基本的PWM输出后,这个项目可以轻松扩展到更多有趣和实用的场景:
7.1 多通道同步输出
TIM3有4个通道(CH1-CH4)。你可以用几乎相同的配置方法,初始化其他通道(如CH1在PA6,CH2在PA7,CH4在PB1),让它们输出相同频率、不同占空比或相位差的PWM波。这对于控制多个LED实现流水灯、控制多路舵机、驱动H桥电路等非常有用。关键是它们共享同一个时基单元(ARR, PSC),因此频率绝对同步。
7.2 与中断结合实现复杂控制
你可以使能定时器的更新中断(UEV)或捕获/比较中断(CCxIE)。
- 更新中断:每个PWM周期结束时进入一次中断。可以在这里安全地更新所有通道的CCR值(占空比),实现复杂的波形序列生成。
- 比较中断:当计数器CNT等于某个通道的CCR值时触发。可以用于在PWM周期内的特定时刻执行某些操作,比如在PWM波形的中点采样一个传感器信号。
7.3 驱动各类负载
- LED调光:直接通过限流电阻驱动小功率LED,改变占空比即可无级调节亮度。频率建议在100Hz以上以避免闪烁。
- 舵机控制:舵机需要的是周期为20ms(50Hz),高电平脉宽在0.5ms到2.5ms之间的PWM信号。用本文的方法,计算出合适的ARR和PSC,然后通过一个按键或ADC采样值来动态改变CCR(脉宽),即可控制舵机角度。
- 直流电机调速:需要通过电机驱动芯片(如L298N、TB6612)或MOS管桥来驱动。MCU的PWM信号接到驱动芯片的使能或输入引脚,通过改变占空比来调节电机两端的平均电压,从而实现调速。电机是感性负载,PWM频率不宜太低(通常几kHz到几十kHz),以避免可闻噪音和电流波动过大。
- 蜂鸣器发声:用PWM驱动无源蜂鸣器,通过快速切换占空比(实际上更常用的是切换频率)可以产生不同音调的声音。此时PWM的频率就在音频范围内(几百Hz到几千Hz)。
配置PWM的过程,本质上是在理解和配置STM32定时器这个强大外设的各个功能单元。从时钟源选择、预分频、自动重装载,到输出比较模式、极性、预装载,每一步都环环相扣。通过这个具体的“TIM3_CH3 -> PB0”项目上手实践一遍,你对STM32定时器的理解会上一个坚实的台阶。以后遇到需要其他定时器、其他模式(如输入捕获、编码器接口)时,你会发现底层逻辑是相通的,查阅手册和库函数指南会更加得心应手。