资讯动态

STM32F103标准库配置TIM3_CH3输出PWM信号全流程详解

发布时间:2026/8/26 5:00:37 来源:尧图企业网站定制
1. 项目概述与核心需求最近在做一个基于STM32F103的小型电机调速项目核心需求之一就是生成一路稳定可靠的PWM信号。手头的开发板资源有限主控是常见的STM32F103C8T6我需要将PB0引脚配置为PWM输出。翻看数据手册和原理图发现PB0恰好是定时器TIM3的通道3CH3的复用功能引脚。这个组合在标准库开发中非常典型但配置过程涉及到时钟使能、GPIO初始化、定时器模式设置、通道输出配置等多个环节任何一个细节疏忽都可能导致没有波形输出或者波形参数不对。今天我就把用STM32标准库函数一步步实现TIM3_CH3在PB0上输出PWM的完整过程、背后的原理以及我踩过的坑详细梳理一遍。无论你是刚开始接触STM32 PWM的新手还是想快速回顾某个配置细节的老手这篇内容都能提供一个清晰的“抄作业”模板。我们会从最基础的时钟树讲起直到用示波器看到正确的波形中间会穿插我调试时遇到的几个典型问题及解决方法。最终目标是让你不仅能配置出来更能理解为什么要这样配置。2. 硬件连接与原理分析2.1 引脚复用功能映射STM32的引脚功能非常灵活一个物理引脚如PB0可以作为通用输入输出GPIO也可以复用于各种外设功能比如这里的定时器通道。具体映射关系需要查阅芯片的数据手册Datasheet中的“Pinouts and pin descriptions”章节以及参考手册Reference Manual中的“Alternate function mapping”表格。对于STM32F103C8T6PB0这个引脚其默认功能是GPIOB的第0位。它的复用功能Alternate Function之一是TIM3的通道3CH3。更具体地说是TIM3_CH3的部分重映射Partial Remap输出引脚。这里需要特别注意TIM3的通道3和通道4在重映射后输出引脚会发生变化。对于TIM3_CH3不复用No Remap输出在PB0这是我们本次使用的。部分重映射Partial Remap输出会切换到PC8。完全重映射Full Remap输出会切换到PC8对于CH3部分和完全重映射结果一样但涉及的其他通道不同。注意在标准库中我们通过GPIO_PinRemapConfig函数来配置重映射。如果我们想使用PB0作为TIM3_CH3就必须禁止重映射或者更准确地说选择“不复用”模式。这是第一个容易出错的地方。2.2 PWM生成的基本原理PWMPulse Width Modulation脉冲宽度调制本质上是一种通过数字手段模拟模拟量的方法。定时器是生成PWM的硬件核心。其工作原理可以类比为一个不断循环的沙漏和一把可移动的闸刀自动重装载寄存器ARR决定了沙漏的总沙子量即PWM波的周期Period。计数器从0开始向上计数数到ARR值后归零重新开始形成一个周期。捕获/比较寄存器CCRx这就是那把“闸刀”的位置。我们设置一个比较值CCR。计数器CNT就像不断落下的沙子其值实时变化。比较匹配当计数器CNT的值小于CCR时我们让输出引脚保持一种状态例如高电平当CNT的值大于或等于CCR但小于等于ARR时让输出引脚保持另一种状态例如低电平。占空比Duty Cycle高电平时间占整个周期的比例。占空比 CCR / (ARR 1)。通过改变CCR的值就能线性地改变输出波形的占空比从而控制平均电压或功率。STM32的定时器硬件自动完成了上述比较和输出翻转的过程我们只需要配置好ARR和CCRCPU负担极低。2.3 定时器时钟源分析定时器要工作必须有时钟“驱动”。STM32F103的定时器时钟APB1或APB2总线时钟可能经过一个倍频器。TIM2、TIM3、TIM4挂在APB1总线上。APB1的默认最高频率是36MHz对于72MHz系统时钟。如果APB1的预分频系数为1则定时器时钟CK_INT直接等于APB1时钟。如果APB1预分频系数不为1通常为2则定时器时钟会是APB1时钟的2倍。这是STM32设计中的一个特点目的是在低速总线上也能让定时器获得较高的时间精度。 在我们的系统初始化代码SystemInit中通常设置SYSCLK72MHzAHB72MHzAPB136MHz预分频系数为2APB272MHz。因此挂载在APB1上的TIM3其内部时钟CK_INT实际为36MHz * 2 72MHz。这个72MHz就是计算PWM频率的基础。3. 软件配置详细步骤下面进入实操环节使用STM32标准外设库StdPeriph_Lib进行配置。假设你已经建立好了包含标准库文件的基础工程。3.1 工程文件与头文件准备首先确保你的工程中包含了必要的库文件并在主文件如main.c开头包含相关头文件#include stm32f10x.h // 核心头文件 #include stm32f10x_gpio.h #include stm32f10x_tim.h #include stm32f10x_rcc.h // 时钟控制头文件3.2 系统时钟与外设时钟使能任何外设使用前必须先打开其对应的时钟门控否则寄存器访问无效。void RCC_Configuration(void) { // 1. 使能GPIOB的时钟因为我们要用PB0 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 2. 使能TIM3的时钟TIM3挂在APB1总线上 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 3. 可选但重要使能AFIO时钟因为我们要配置引脚复用功能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); }实操心得很多初学者调试时发现程序卡死或功能无效第一步就应该检查时钟使能函数是否被调用。AFIO复用功能IO的时钟经常被遗忘特别是在配置重映射或EXTI时必须开启。3.3 GPIO引脚模式配置接下来将PB0配置为复用推挽输出模式。因为TIM3_CH3要控制这个引脚输出波形所以它不再是普通的GPIO输出而是作为外设的复用功能输出。void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; // 选择要配置的引脚PB0 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; // 配置为复用推挽输出模式 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 输出速度设为50MHz对于PWM输出高速模式更可靠 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; // 应用配置到GPIOB端口 GPIO_Init(GPIOB, GPIO_InitStructure); }为什么是GPIO_Mode_AF_PPAF代表Alternate Function复用功能表示该引脚由片上外设TIM3控制。PP代表Push-Pull推挽输出这是数字输出的标准模式能提供明确的强驱动高电平和低电平驱动能力强于开漏模式适合直接驱动LED、作为信号源等场景。3.4 定时器基本参数与时基初始化这是配置的核心决定了PWM波的频率周期。void TIM3_Configuration(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 计算目标PWM频率。假设我们想要一个1kHz的PWM波。 // 定时器时钟CK_INT 72MHz (APB1时钟36MHz经过x2倍频) // PWM频率 CK_INT / ((ARR 1) * (PSC 1)) // 我们令预分频器PSC 0即不分频。则公式简化为频率 72MHz / (ARR 1) // 所以 ARR (72MHz / 频率) - 1 // 对于1kHz: ARR (72,000,000 / 1000) - 1 71999 uint16_t PrescalerValue 0; uint16_t PeriodValue 71999; // 对应1kHz // 配置时基单元 TIM_TimeBaseStructure.TIM_Period PeriodValue; // 自动重装载值ARR TIM_TimeBaseStructure.TIM_Prescaler PrescalerValue; // 预分频器PSC TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; // 时钟分频与死区时间相关通常设为DIV1 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // 向上计数模式最常用 TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); }参数选择背后的考量TIM_Prescaler对定时器时钟进行第一次分频。如果PWM频率很低比如几十Hz而ARR最大值只有65535对于16位定时器可能不够除。这时就需要增大PSC值降低计数器的计数频率CK_CNT。公式是CK_CNT CK_INT / (PSC 1)。TIM_Period即ARR值决定了计数器的上限从而决定了PWM的周期。PWM周期 (ARR 1) * (PSC 1) / CK_INT。TIM_CounterMode向上计数是最直观的模式计数器从0到ARR然后溢出归零非常适合生成对称的PWM。3.5 PWM输出通道配置配置定时器为PWM输出模式并设置初始占空比。void TIM3_PWM_Configuration(void) { TIM_OCInitTypeDef TIM_OCInitStructure; // 配置TIM3的通道3为PWM模式 // 注意TIM_OCMode_PWM1 和 TIM_OCMode_PWM2 的区别在于电平极性 // PWM1: 当计数器CNT CCRx时通道为有效电平可配置CNT CCRx时为无效电平。 // PWM2: 逻辑相反。 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; // 输出比较预装载使能。这个一定要设为ENABLE // 它意味着你对CCR的修改会先写入一个缓冲寄存器等到下一个更新事件UEV时才生效。 // 这可以防止在PWM周期中间修改占空比导致波形出现毛刺或断裂。 TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; // 使能通道输出 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; // 输出极性高电平为有效电平 TIM_OCInitStructure.TIM_Pulse 0; // 初始的捕获/比较值CCR即初始占空比。这里先设为0。 // 使用PWM模式1初始化通道3 TIM_OC3Init(TIM3, TIM_OCInitStructure); // 使能通道3的预装载寄存器CCR3 TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能定时器ARR的预装载寄存器 TIM_ARRPreloadConfig(TIM3, ENABLE); // 最后启动定时器3的计数器 TIM_Cmd(TIM3, ENABLE); }关键点解析TIM_OCMode_PWM1 vs PWM2这决定了有效电平占空比对应的电平和比较结果的对应关系。通常我们使用PWM1并配合TIM_OCPolarity_High意味着“当CNTCCR时输出高电平”。如果你接的负载是低电平有效比如某些LED模块共阳极接法你可以选择PWM2或者保持PWM1但将极性设为Low。预装载Preload的重要性TIM_OCxPreloadConfig和TIM_ARRPreloadConfig务必使能。这保证了你在程序运行中修改CCR或ARR时新值不会立即生效干扰当前周期而是等到当前周期结束更新事件后才切换确保了PWM波形的完整性和平滑性这是生成稳定PWM的关键。TIM_Pulse这个值就是初始的CCR值。我们可以在主循环中动态修改它来改变占空比。占空比 TIM_Pulse / (TIM_Period 1)。这里初始设为0意味着输出常低占空比0%。3.6 主函数整合与动态调占空比将上述初始化函数在main函数中调用并添加一个简单的动态改变占空比的逻辑。int main(void) { // 系统初始化通常由启动文件调用SystemInit已设置好72MHz时钟 // 初始化外设时钟 RCC_Configuration(); // 初始化GPIO GPIO_Configuration(); // 初始化TIM3时基 TIM3_Configuration(); // 配置TIM3的PWM输出通道 TIM3_PWM_Configuration(); // 设置一个初始占空比例如50% // TIM3的周期是72000 (719991) // 50%占空比对应的CCR值 72000 * 0.5 36000 TIM_SetCompare3(TIM3, 36000); while (1) { // 这里可以添加动态改变占空比的代码 // 例如通过按键、ADC采样值或者一个简单的循环来修改CCR3 // TIM_SetCompare3(TIM3, new_ccr_value); // 注意new_ccr_value 必须小于等于 TIM3-ARR (即71999) } }TIM_SetCompare3(TIM3, value)这个库函数是动态修改占空比最安全、最常用的方法它内部会处理预装载寄存器的写入。4. 调试技巧与常见问题排查即使代码看起来正确第一次上电也可能看不到预期的波形。以下是我在调试中总结的排查清单按照从硬件到软件、从简单到复杂的顺序进行。4.1 硬件连接检查引脚确认用万用表或查看原理图百分百确认你的MCU的PB0引脚连接到了你想测量或驱动的地方如示波器探头、LED、电机驱动芯片输入。共地示波器或逻辑分析仪的探头地线一定要和你的开发板共地GND否则测量到的信号是浮空的没有参考点。电源与复位确保MCU供电稳定复位电路正常。可以尝试让程序先跑一个简单的GPIO闪烁LED的程序验证最小系统是否工作。4.2 软件逻辑与配置排查如果硬件无误问题大概率在软件配置。问题1完全没有波形输出引脚一直是低电平或高电平。检查时钟确认RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE)和RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE)确实被调用。可以在初始化后添加一个简单的while(1)循环用调试器查看RCC-APB1ENR和RCC-APB2ENR寄存器的对应位是否被置1。检查GPIO模式确认GPIO初始化为GPIO_Mode_AF_PP复用推挽输出而不是GPIO_Mode_Out_PP通用推挽输出。如果错配为通用输出定时器无法控制该引脚。检查AFIO时钟与重映射确认RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE)已调用。虽然我们不进行重映射但复用功能本身可能也需要AFIO时钟。更关键的是检查是否有其他地方意外地使能了重映射。可以在初始化代码中显式地禁止重映射GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, DISABLE);检查定时器是否使能确认TIM_Cmd(TIM3, ENABLE)被执行。检查输出状态确认TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable。问题2有波形输出但频率不对。计算错误仔细核对PWM频率计算公式。PWM频率 CK_INT / ((ARR 1) * (PSC 1))。其中CK_INT需要根据你的系统时钟和APB1预分频系数计算清楚。用示波器测量实际周期反推计算用的时钟频率是否正确。系统时钟未正确配置如果你的工程没有调用SystemInit()或者自己修改了时钟配置系统可能运行在默认的8MHz HSI时钟上导致所有频率都慢9倍。检查SystemCoreClock全局变量的值。问题3有波形输出频率正确但占空比不可调或调节异常。CCR值超出范围确保你通过TIM_SetCompare3设置的new_ccr_value小于等于TIM3-ARR即Period。如果大于ARR占空比将永远是100%。预装载未使能如果未使能TIM_OC3PreloadConfig在修改CCR时可能会破坏正在进行的PWM周期导致波形出现短暂异常。确保它被启用。极性理解错误如果你设置的是TIM_OCPolarity_Low那么“有效电平”是低电平。此时CCR值越大低电平时间越长高电平时间越短视觉上可能与你“占空比增大亮度增强”的直觉相反。请根据你的硬件电路理解极性。问题4波形抖动或有毛刺。中断干扰如果系统中有高优先级中断频繁发生可能会打断PWM相关寄存器的写入操作虽然概率低。检查中断服务程序ISR是否过于耗时。电源噪声电机等大功率负载启停会对MCU电源造成干扰。确保电源去耦电容通常为0.1uF和10uF靠近MCU的VDD和VSS引脚焊接良好。示波器探头问题探头接地不良或带宽不足也会导致观测到虚假的毛刺。尝试将探头接地线缩短使用探头自带的接地弹簧而非长鳄鱼夹。4.3 使用调试器辅助排查现代IDE如Keil MDK、IAR或STM32CubeIDE的调试功能非常强大。外设寄存器视图在调试模式下打开TIM3和GPIOB的外设寄存器窗口。你可以实时查看TIM3-CR1确认CEN位Counter ENable为1定时器在运行。TIM3-CCER确认对应通道CC3E位为1输出使能。TIM3-CCR3确认其值是否与你设置的一致并且在动态修改时是否正确变化。GPIOB-CRL确认PB0引脚的模式配置寄存器是否为“复用推挽输出”模式通常对应值0xB具体查手册。逻辑分析仪如果你的开发环境支持如STM32的SWD接口配合某些软件或者有独立的逻辑分析仪可以捕获引脚的实际电平变化与理论波形对比这是最直接的调试手段。5. 进阶应用与优化建议掌握了基础输出后可以探索更复杂的应用让PWM发挥更大作用。5.1 多通道同步输出TIM3有4个通道CH1-CH4。你可以用几乎相同的配置初始化其他通道如CH4在PB1上实现多路同步PWM输出。这对于控制全桥电路如H桥驱动直流电机至关重要需要精确控制多路信号的相位和死区时间。// 初始化CH4 (PB1) TIM_OCInitStructure.TIM_Pulse some_initial_value; TIM_OC4Init(TIM3, TIM_OCInitStructure); TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);注意同一个定时器的所有通道共享同一个ARR频率但各有独立的CCRx占空比。因此它们输出的PWM波频率相同占空比可以独立设置。5.2 动态调整频率与占空比调整占空比使用TIM_SetComparex(TIM3, new_ccr)函数安全且简单。调整频率周期需要同时修改ARR和可能涉及的PSC。为了波形平滑建议停止计数器TIM_Cmd(TIM3, DISABLE);修改TIM3-ARR和TIM3-PSC。如果新的ARR值小于任何通道当前的CCRx需要先手动将CCRx调整为小于新ARR的值否则该通道会输出常高。重新使能计数器TIM_Cmd(TIM3, ENABLE);由于我们使能了ARR预装载也可以在运行时直接修改TIM3-ARR但同样要注意CCRx的关系。5.3 加入死区时间控制在驱动半桥或全桥电路时为了防止上下桥臂直通短路需要在互补的PWM信号之间插入一段两个信号都为低电平的“死区时间”。STM32的高级定时器TIM1, TIM8和通用定时器都支持硬件死区时间插入。这需要配置TIMx-BDTR寄存器。对于TIM3虽然它是通用定时器但部分型号也支持简单的死区时间生成或者可以通过复杂的通道组合和输出模式模拟。不过对于严格的电机驱动建议直接使用高级定时器TIM1。5.4 使用中断或DMA更新PWM中断更新可以开启定时器的更新中断TIM_IT_Update在中断服务程序中修改CCR值实现复杂的PWM波形序列。DMA更新这是更高效的方式。可以将一个存储了多个CCR值的数组通过DMA自动、周期性地搬运到TIM3-CCR3寄存器中实现“任意波形”的PWM输出而无需CPU干预。这对于LED呼吸灯渐变、步进电机细分驱动等场景非常有用。配置PWM输出是STM32开发中的一项基本功从简单的指示灯调光到复杂的电机控制、电源管理都离不开它。理解从时钟源到引脚输出的完整数据路径掌握每个配置步骤的意义是解决问题的关键。遇到问题时按照“时钟-GPIO-定时器基础-输出比较-使能”的顺序进行排查同时善用调试工具观察寄存器状态大部分问题都能迎刃而解。最后别忘了数据手册和参考手册是你最权威的参考资料当库函数让你感到困惑时直接查阅寄存器描述往往能获得最清晰的认识。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价