前言

          第六章 TIM 定时器主要包含以下四个部分:

  • 第一部分:定时器基本定时功能(已学习)
  • 第二部分:定时器输出比较功能(本篇内容)
  • 第三部分:定时器输入捕获功能
  • 第四部分:定时器编码器接口功能

          其中,输出比较是本章的核心内容之一,因为它是 STM32 产生 PWM 波形的基础。几乎所有涉及“功率控制”或“模拟量控制”的应用,背后都离不开 PWM 技术。因此,掌握输出比较功能,是使用 STM32 进行电机控制、机器人开发等项目的基础。


目录

前言

1 输出比较模式

1.1 简介

1.2 工作原理

第一阶段:时基单元运行(Time Base)—— 决定波形的频率

第二阶段:比较单元判定(Comparator)—— 决定波形的占空比

第三阶段:输出模式控制(Mode Controller)—— 生成中间参考信号 OCxREF

第四阶段:输出控制电路(Output Stage)—— 极性选择与引脚驱动

总结

1.3 输出比较的8种模式

1.4 关键寄存器介绍

3 PWM 简介

3.1 什么是PWM?

3.2 PWM 波形的形成机制(STM32 实现原理)

3.3 PWM 的关键参数与计算

3.3.1 频率 (Frequency)

3.3.2 占空比 (Duty Cycle)

3.3.3 分辨率 (Resolution)

3.3.4 参数配置实例:确定相关寄存器的值

3.4 PWM在惯性系统中的应用

4 PWM 输出的配置与实现

4.1 参数选型:锁定寄存器配置值

4.2 PWM初始化流程

4.2.1 开启时钟

4.2.2 配置 GPIO (复用推挽输出)

4.2.3 配置时基单元

4.2.4 配置输出比较单元

4.2.5 启动定时器

4.3 动态调整占空比(实现呼吸灯)

4.3.1 预装载与影子寄存器机制

4.4 PWM引脚重映射(Remap)

4.4.1 映射方案选择与引脚分析

4.4.2 资源冲突深度解析:PA15 与 JTAG

4.4.3 重映射配置步骤

4.4.4 完整代码示例

4.4.5 配置要点与排错指南

5 相关元器件简介

5.1 SG90 9g 舵机(伺服电机)

5.1.1 简介

5.1.2 内部闭环控制原理

5.1.3 PWM 控制协议与角度映射

5.1.4 基于 STM32 的 PWM 参数计算

5.1.5 角度与 CCR 值的线性转换逻辑

5.1.6 进阶探讨:舵机抖动原因与多路控制策略

5.2 130电机马达

5.2.1 物理架构与电磁转换机理

5.2.2 机电特性参数分析

5.2.3 硬件接口约束与保护逻辑

5.2.3.1 功率放大与驱动隔离

5.2.3.2 反电动势及其抑制

5.2.3.3 电源噪声抑制

5.3 TB6612FNG电机驱动模块

5.3.1 模块架构与技术优势

5.3.2 硬件接口与引脚定义

5.3.3 控制逻辑真值表与状态解析

5.3.4 实现结论与工程建议

6 本章节实验

6.1 PWM驱动LED呼吸灯

6.1.1 实验目标

6.1.2 硬件设计

6.1.3 软件设计

6.1.4 实验现象

6.2 PWM驱动舵机

6.2.1 实验目标

6.2.2 硬件设计

6.2.3 软件设计

6.2.4 实验现象

6.3 PWM驱动直流电机

6.3.1 实验目标

6.3.2 硬件设计

6.3.3 软件设计

6.3.4 实验现象


1 输出比较模式

1.1 简介

          输出比较(Output Compare,简称OC)是STM32定时器的一项核心功能,广泛应用于PWM波形生成、精确延时控制、外部事件触发等场景。其基本原理可以概括为:将定时器的计数器(CNT)与预先设定的捕获/比较寄存器(CCR)进行比较,当两者满足特定关系(如相等、大于或小于)时,通过硬件自动翻转 GPIO 引脚的电平,从而生成精确的波形。

          通俗地说,输出比较就像给定时器设置了一个“闹钟”:当计数器走到我设定的数值(CCR)时,引脚就自动执行一个动作(比如翻转电平、输出高电平或低电平)。这种自动化的硬件机制,使得生成精确的PWM波形变得简单高效。

          STM32 通用定时器(如 TIM2-TIM5)的结构框图如下所示,图中标示的“输出比较部分”即为通用定时器的输出比较模块电路,包含 4 个独立的输出比较通道,它们共用同一个计数器,但每个通道都有独立的 CCR 寄存器和输出引脚。


1.2 工作原理

          输出比较的本质,是利用 CNT 与 CCR 的数值关系,通过一系列硬件电路的处理,最终在 GPIO 上输出特定的电平。如图所示:

          我们可以将这个过程拆解为以下四个核心阶段:

第一阶段:时基单元运行(Time Base)—— 决定波形的频率

          定时器启动后,计数器 CNT 在时钟驱动下开始运行。CNT 的计数模式决定了波形的基础周期,常见的模式有:

  • 向上计数模式:CNT 从 0 递增至自动重装载值(ARR),发生溢出后清零,循环往复。这是生成 PWM 最常用的模式。如下图所示:

  • 向下计数模式:CNT 从 ARR 递减至 0,然后重新加载 ARR 再次递减;
  • 中心对齐计数模式:CNT 先从 0 递增至 ARR,再从 ARR 递减至 0,循环往复;

          关键点:ARR(自动重装载寄存器)的值决定了计数器的溢出周期,也就是最终输出波形的周期(频率)。

第二阶段:比较单元判定(Comparator)—— 决定波形的占空比

          在 CNT 运行过程中,硬件电路会实时比较 CNT(当前计数值)与 CCR(预设比较值)的大小。比较结果通常分为三种情况:

  • CNT < CCR
  • CNT = CCR
  • CNT > CCR

          这个比较结果是生成 PWM 信号的原始依据。CCR 的值决定了电平翻转的时间点,也就是最终波形的占空比

第三阶段:输出模式控制(Mode Controller)—— 生成中间参考信号 OCxREF

          比较结果出来后,并不是直接输出到引脚,而是先送入“输出模式控制器”。根据寄存器 TIMx_CCMRxOCxM 位的配置(如 PWM 模式 1、PWM 模式 2、翻转模式等),生成一个中间参考信号 OCxREF (Output Compare Reference)。如图所示:

          OCxREF 是一个逻辑信号(高电平/低电平),代表了当前通道在逻辑上是“有效”还是“无效”。例如,在向上计数模式下,PWM模式1的规则如下:

  • 当 CNT < CCR 时:OCxREF = 1(有效状态)
  • 当 CNT ≥ CCR 时:OCxREF = 0(无效状态)

第四阶段:输出控制电路(Output Stage)—— 极性选择与引脚驱动

          OCxREF 信号还需要经过最后一道关卡才能到达 GPIO 引脚,这部分电路主要负责极性调整输出使能

          极性选择(Polarity) 由寄存器 TIMx_CCERCCxP 位控制。它决定了“有效电平”对应的是物理上的高电平还是低电平。逻辑关系如下:

CCxP 配置逻辑关系结果说明
0正极性 (同相)OCxREF 为高 -> 引脚输出 高电平
1负极性 (反相)OCxREF 为高 -> 引脚输出 低电平

          输出使能(Enable)TIMx_CCERCCxE 位控制。

  • CCxE = 1:开启输出开关,信号通过 GPIO 引脚(如 PA0)输出。
  • CCxE = 0:关闭输出。

          只有在输出使能的情况下,信号才会真正出现在对应的定时器输出引脚 TIMx_CHx 上。例如,TIM2_CH1默认映射到PA0引脚。

总结

          将上述流程串联起来,我们可以清晰地看到 PWM 波形的生成路径:

  1. CNT0ARR 之间循环计数(决定频率)。
  2. CCR 设定了一个阈值切割点。
  3. 比较器 根据 CNT 与 CCR 的关系,输出逻辑电平。
  4. 极性电路 决定最终的高低电平。

          最终效果: 随着 CNT 的周期性变化,输出引脚 TIMx_CHx 上会产生连续的脉冲波形。我们只需要通过软件修改 CCR 寄存器的值,就可以在不停止定时器的情况下,平滑地改变输出波形的占空比,实现对电机速度或 LED 亮度的精准控制。如图所示:


1.3 输出比较的8种模式

          输出比较模式的核心在于:当CNT与CCR的比较结果满足条件时,如何控制OCxREF的电平。这些模式通过配置捕获/比较模式寄存器(TIMx_CCMRx)中的OCxM[2:0]位来实现,共有8种模式。下表详细列出了这8种模式及其典型应用:

OCxM[2:0]配置模式名称功能描述典型应用
000冻结比较匹配时,OCxREF保持原状态,不受比较结果影响。暂停波形输出,维持当前电平
001匹配时置有效电平当CNT = CCR时,OCxREF强制为有效电平(高电平)。产生固定宽度的正脉冲
010匹配时置无效电平当CNT = CCR时,OCxREF强制为无效电平(低电平)。产生固定宽度的负脉冲
011匹配时翻转当CNT = CCR时,OCxREF电平翻转。生成50%占空比的方波信号
100强制为无效电平无视比较结果,强制OCxREF为无效电平(低电平)。强制关闭输出
101强制为有效电平无视比较结果,强制OCxREF为有效电平(高电平)。强制开启输出
110PWM模式1向上计数时:CNT < CCR时输出有效电平,CNT ≥ CCR时输出无效电平。最常用模式,生成频率和占空比可调的PWM波形
111PWM模式2向上计数时:CNT < CCR时输出无效电平,CNT ≥ CCR时输出有效电平。生成与PWM1极性相反的PWM波形

          模式说明:

  • 冻结模式:当你正在输出PWM波,想暂停输出时,可以临时切换为冻结模式,输出将维持在暂停时刻的电平状态。
  • 匹配时翻转模式:可以方便地输出一个频率可调、占空比始终为50%的PWM波形。例如设置CCR=0,则每次CNT更新清零时都会产生比较匹配,电平翻转一次。每更新两次,输出为一个周期,且高低电平时间相等。
  • PWM模式1和PWM模式2:两者本质上是互补关系——它们产生的波形正好极性相反。在实际应用中,通常只需掌握PWM模式1,配合极性选择功能即可灵活实现各种需求。我们后续的实验也将主要使用PWM模式1。

1.4 关键寄存器介绍

          配置和使用输出比较功能,主要涉及以下几个关键寄存器:

寄存器名称作用描述
TIMx_CNT计数器核心计数器,不断递增或递减,其值反映时间流逝。
TIMx_CCR捕获/比较寄存器存放用户设定的比较值,是决定输出波形特征(如PWM占空比)的关键。每个通道有自己的CCR寄存器。
TIMx_ARR自动重装载寄存器决定计数器的计数周期,即PWM波形的频率。计数器计数到ARR后溢出重装。
TIMx_PSC预分频器对定时器的时钟源进行分频,用于调整计数频率。
TIMx_CCMRx捕获/比较模式寄存器配置通道为输出模式(CCxS位);选择上述8种输出比较模式之一(OCxM位);配置输出预装载功能(OCxPE位)等。
TIMx_CCER捕获/比较使能寄存器使能输出通道(CCxE位);配置输出极性(CCxP位),决定有效电平对应高电平还是低电平。
TIMx_CR1控制寄存器1使能定时器计数器(CEN位);设置计数方向(DIR位)等。
TIMx_SR状态寄存器当比较匹配发生时,相应的CCxIF标志位会被置1。
TIMx_DIERDMA/中断使能寄存器使能比较匹配中断(CCxIE位)或DMA请求(CCxDE位)。

          关于CCR预装载功能:TIMx_CCRx寄存器有影子寄存器,可以通过OCxPE位控制是否启用预装载功能。当启用时,写入CCR的新值不会立即生效,而是在下一次更新事件(UEV)时才加载到影子寄存器,这样可以避免在周期中间突然改变比较值而导致波形异常。


3 PWM 简介

3.1 什么是PWM?

          PWM(Pulse Width Modulation),全称为脉冲宽度调制,是一种利用微处理器的数字输出信号实现模拟量控制的技术。简而言之,PWM 是一种将模拟信号电平进行数字化编码的方法。

          单片机的 IO 口在任意时刻只能输出两种离散状态:高电平(如 3.3V)或低电平(0V),属于典型的数字信号,无法直接输出连续变化的模拟电压。然而,如果让 IO 口按照固定周期不断输出高低电平,并通过调节高电平在周期中的持续时间比例(占空比),就可以改变信号的平均电压值,使其在整体效果上等效为一个介于 0V 和 3.3V 之间的连续模拟量。

          从原理上看,这种等效关系可以用面积等效法来解释:在一个固定周期内,PWM 波形的电压-时间面积,与目标模拟电压在同一周期内的面积相等,因此两者具有相同的平均电压效果。如下图所示:PWM 对应模拟信号的等效图。

          为了更直观地理解这一点,可以引入惯性系统的概念。所谓惯性系统,是指其响应无法跟随输入信号的高速变化,而是表现为对输入信号的平均响应。例如,控制一个 LED 以极高频率(如每秒 1000 次)快速开关:

  • 如果 LED 在 50% 的时间内点亮,50% 的时间内熄灭;
  • 由于人眼存在视觉暂留效应(视觉惯性),无法分辨这种高速闪烁;
  • 最终看到的不是闪烁,而是一个亮度约为全亮一半的稳定光。

          从能量等效角度来看,这相当于给 LED 施加了一个等效电压:3.3V×50%=1.65V

          同样的原理也适用于其他具有惯性的系统,例如:

  • 电机的转动惯量,使其转速反映平均电压;
  • 电容的充放电特性,使输出电压趋于平滑;
  • 低通滤波电路,可将 PWM 转换为稳定的模拟电压。

          因此,只要 PWM 的开关频率足够高,负载就无法跟随其瞬时变化,而只表现出平均效果,从而实现用数字信号输出模拟量的目的。

提示:上文中的“惯性”与电路中的惯性系统的关系

          这里的“惯性”其实是一个广义的低通滤波概念。例如:

  • 物理/机械惯性:如电机的转子质量,它无法随高频信号瞬间启停,只能响应能量的平均值。
  • 生物惯性:如人眼的视觉暂留,本质上是神经感官对光线变化的滞后反应。
  • 电路惯性:也就是我们在电路书籍中常看到的电容(电压不能突变)和电感(电流不能突变)。

          它们本质上都是在扮演“储能缓冲”的角色。正是因为有了这些惯性(滤波效应),PWM的高频方波才能被平滑成稳定的模拟量。如果没有惯性,PWM就只是无意义的快速开关切换。


3.2 PWM 波形的形成机制(STM32 实现原理)

这里与输出比较的工作原理一节高度重合,因为都是以产生PWM波形为目的去解释其工作原理的,所以这里只大致介绍一下。

          在 STM32 中,PWM 波形的产生依赖于定时器内部两个核心寄存器的配合:计数器 (CNT)捕获/比较寄存器 (CCR)

          工作流程大致分以下三个阶段:

          (1)计数阶段:定时器启动后,计数器 CNT 在时钟驱动下从 0 开始不断累加,直到达到自动重装载值 ARR,然后清零重新开始。这个周期性的计数过程形成了一个“锯齿波”或“三角波”的时间基准。

          (2)比较阶段:在 CNT 计数的过程中,硬件电路会实时将 CNT(当前值) 与 CCR(预设比较值) 进行比较:

  • 当 CNT < CCR 时:输出有效电平(例如高电平)。
  • 当 CNT ≥ CCR 时:输出无效电平(例如低电平)。

          (3)波形输出:随着 CNT 的周期性变化,输出引脚上就会自动产生出一连串脉宽固定的方波。如图所示:

  • 改变 ARR:改变了计数周期的长短,即调整了 PWM 的频率。
  • 改变 CCR:改变了电平翻转的阈值点,即调整了 PWM 的占空比。

          这种机制完全由硬件自动完成,无需 CPU 干预,因此效率极高。


3.3 PWM 的关键参数与计算

          描述一个 PWM 信号,离不开三个核心参数:频率占空比分辨率。在 STM32 开发中,它们与硬件寄存器(PSC、ARR、CCR)存在严格的数学对应关系。

3.3.1 频率 (Frequency)

          指 1 秒钟内 PWM 信号周期的个数。频率越高,等效输出的模拟信号越平滑,纹波越小,但系统性能开销也越大。

          物理定义:

f = \frac{1}{T}

          计算公式:

$f_{PWM} = \frac{CK\_PSC}{(PSC + 1) \times (ARR + 1)}$

  • CK_PSC:定时器的输入时钟频率(通常为 72MHz)。
  • PSC:预分频系数。
  • ARR:自动重装载值(决定周期长度)。

3.3.2 占空比 (Duty Cycle)

          指一个周期内,有效电平(高电平)持续时间占整个周期的比例。它直接决定了等效输出的模拟电压的大小。占空比越大,平均电压越高。

          物理定义:

$Duty = \frac{t_{on}}{T_{S}} \times 100\%$

          计算公式:

$Duty = \frac{CCR}{ARR + 1}$

3.3.3 分辨率 (Resolution)

          指占空比可调节的最小步长,即 PWM 对输出控制的“细腻程度”。分辨率越高,对电压或速度的控制就越细腻。

          计算公式:

$Resolution = \frac{1}{ARR + 1}$

          所以,ARR 值越大,分辨率越高(步长越小),控制越精细。

3.3.4 参数配置实例:确定相关寄存器的值

          在 STM32 中生成特定的 PWM 波形,本质上是一个逆向推导的过程。我们需要根据应用需求,利用 PWM 的三大核心公式,依次锁定硬件中三个关键寄存器的数值:PSC(预分频器)ARR(自动重装载寄存器)CCR(捕获/比较寄存器)

  • PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  • PWM占空比:  Duty = CCR / (ARR + 1)
  • PWM分辨率:  Reso = 1 / (ARR + 1)

          核心逻辑说明:

  • 确定 ARR:由分辨率(步进精度)决定。
  • 确定 PSC:由目标频率与已确定的 ARR 共同决定。
  • 确定 CCR:由目标占空比与已确定的 ARR 共同决定。

          具体示例如下:

          配置需求:利用 STM32(主频 72MHz)产生一个频率为 1kHz、占空比 50% (可任意调节)、分辨率为 1% 的 PWM 波形。计算步骤如下:

(1)确定 ARR (由分辨率决定)

          分辨率定义为占空比变化的最小步长。要求 1% 的分辨率,意味着需要将一个完整的周期等分成 100 份。

$Reso = \frac{1}{ARR + 1}\Rightarrow \mathbf 1\% = \frac{1}{100} \Rightarrow \mathbf ARR + 1 = 100 \Rightarrow \mathbf{ARR = 99}$

(2)确定确定 PSC (由目标频率决定)

          目标 1kHz,且ARR = 99,代入频率公式:

$Freq = \frac{f_{CLK}}{(PSC + 1)(ARR + 1)}\Rightarrow \mathbf1000 = \frac{72,000,000}{(PSC + 1) \times 100}$

          解得:

$(PSC + 1) = 720 \Rightarrow \mathbf{PSC = 719}$

(3)确定 CCR (由目标占空比决定)

          假设我们需要初始输出 50% 占空比的波形。已知: ARR = 99,PSC = 719。代入占空比公式:

$Duty = \frac{CCR}{ARR + 1}\Rightarrow \mathbf50\% = \frac{CCR}{100} \Rightarrow \mathbf CCR = 50$

          最终的硬件参数设置如下:

寄存器名称设定值对应物理含义
PSC719将 72MHz 时钟进行 720 分频,产生 100kHz 的计数频率
ARR99每计满 100 个数溢出一次,产生 1kHz 的 PWM 周期
CCR0 ~ 100调节该值即可在 0% ~100% 范围内改变占空比

          通过这三个值的配合,就能锁定PWM波形的频率和分辨率,并确定其占空比。


3.4 PWM在惯性系统中的应用

          PWM 技术因其高效率和低成本,广泛应用于各类惯性系统中。所谓惯性系统,是指对输入信号变化反应有滞后、能起到“低通滤波”作用的系统。例如:

  • LED 亮度控制(调光) 利用人眼的视觉暂留效应。当 PWM 频率大于 50Hz 时,人眼无法分辨闪烁,只能感知到亮度的变化。这是手机屏幕亮度调节和呼吸灯的核心原理。
  • 直流电机调速 利用电机的机械惯性(转子转动惯量)。电机转速取决于电枢两端的平均电压。相比于传统的电阻分压调速,PWM 方式发热少、能效比极高。
  • 占空比↑ ——> 平均电压↑ ——> 转速加快
  • 占空比↓ ——> 平均电压↓ ——> 转速减慢
  • 开关电源 (SMPS) 通过控制功率管(MOSFET)的导通时间(占空比)来精确控制输出电压。这是所有现代电子设备供电的基础。
  • 音频信号生成 (DAC) 通过高速 PWM 配合 RC 低通滤波器,可以将数字音频流还原为模拟声波,常用于简单的语音播放。

          PWM技术之所以应用如此广泛,得益于其显著的优点:

  • 抗干扰能力强:信号始终在全高或全低电平工作,数字传输不易受模拟噪声影响。
  • 能效高:控制元件(如三极管/MOS管)工作在开关状态,导通时压降小,截止时电流小,显著降低了功耗和发热。
  • 实现简单:现代微控制器(MCU)内部均集成了专用的硬件 PWM 模块,无需复杂的外部电路。

4 PWM 输出的配置与实现

          在掌握了 PWM 的理论基础后,本节进入代码实战环节。我们将以 STM32F103 为例,详细拆解如何配置定时器以输出 PWM 波形,并实现占空比的动态调整。

          在本节的演示中,我们的目标是生成一个具有以下特征的 PWM 波形:

  • 输出引脚:使用 TIM2 的通道 1(默认引脚为 PA0)。
  • 波形规格:频率为 1kHz,占空比 0%~100% 可调,且具有 1% 的调节分辨率。
  • 应用实例:最终通过动态修改占空比,在 PA0 引脚上实现一个平滑的呼吸灯效果。

4.1 参数选型:锁定寄存器配置值

          ​​​​​​​根据实验要求的1kHz频率和1%分辨率,需要先计算出对应的寄存器参数值。基于 3.3.4 节的推导逻辑,我们确定了以下三个关键配置参数,这些参数与STM32硬件寄存器的对应关系如下表所示:

目标需求对应寄存器参数值
调节精度 (1%)ARR (自动重装载值)ARR = 100 - 1 
波形频率 (1kHz)PSC (预分频系数)PSC = 720 - 1 
初始占空比 (0%)CCR (比较值)CCR = 0

          ​​​​​​​通过这组参数配置,定时器的计数频率被设定为 100kHz,每计满 100 个数产生一个 PWM 周期。此时,只需在程序中将 CCR 的值在 0 ~100 之间调整,即可实现精度为 1% 的全范围占空比控制。如图所示:


4.2 PWM初始化流程

          ​​​​​​​配置 PWM 输出通常遵循一套固定的“五步法”。为了便于演示,我们将 TIM2 的通道 1 (CH1) 配置为 PWM 输出,该通道默认映射在 PA0 引脚。

4.2.1 开启时钟

          ​​​​​​​任何外设在使用前都需要先开启其时钟。我们需要分别开启定时器和 GPIO 的时钟。注意它们挂载的总线可能不同。

// 开启TIM2的时钟。TIM2是APB1总线上的外设。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

// 开启GPIOA的时钟,因为我们要使用PA0引脚。GPIOA是APB2总线上的外设。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

4.2.2 配置 GPIO (复用推挽输出)

          ​​​​​​​关键点:要使定时器能够控制GPIO引脚的电平,引脚模式必须配置为 复用推挽输出 (GPIO_Mode_AF_PP)

  • 普通推挽:引脚电平由输出数据寄存器(ODR)控制(即 CPU 软件控制)。
  • 复用推挽:引脚控制权移交给片上外设(定时器),由定时器内部信号直接驱动引脚。如图所示:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;         // 选择要配置的引脚,TIM2_CH1 默认在 PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 设置为复用推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置输出速度为50MHz,这是一个常见的速度选择

GPIO_Init(GPIOA, &GPIO_InitStructure);            // 将配置应用到GPIOA端口

4.2.3 配置时基单元

          ​​​​​​​这一步决定了 PWM 的频率。我们通过TIM_TimeBaseInitTypeDef结构体来配置,主要涉及以下两个成员:

  • TIM_Prescaler:设定预分频器值 PSC;
  • TIM_Period:设定自动重装载值 ARR;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

// 配置时基单元结构体
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     // 时钟分频,一般不分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器向上计数模式
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;                 // 设置自动重装载值ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;              // 设置预分频值PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            // 重复计数器,高级定时器才用到,此处填0

// 将配置写入TIM2
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

4.2.4 配置输出比较单元

          ​​​​​​​这一步决定了 PWM 的模式极性初始占空比。我们使用TIM_OCInitTypeDef结构体进行配置。

TIM_OCInitTypeDef TIM_OCInitStructure;

// 给结构体赋一个初始值,这是一个好习惯,可以避免因未赋值成员导致的意外错误
TIM_OCStructInit(&TIM_OCInitStructure);

// 设置输出比较模式为PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 设置输出极性为高,即有效电平为高电平。如果设置为低,则输出波形会取反
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 使能输出
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 设置初始CCR值,此值决定初始占空比。对于1kHz、ARR=99的PWM,CCR=50对应50%占空比
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比设为0%

// 将配置应用到TIM2的通道1。不同的通道对应不同的函数:TIM_OC1Init、TIM_OC2Init等
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

// 使能TIM2在CCR1上的预装载寄存器。这样,在运行时修改CCR值,会等到下一个更新事件才生效,避免波形异常
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);

注意:先调用 TIM_OCStructInit 给结构体赋默认值,防止未知错误。

4.2.5 启动定时器

          ​​​​​​​最后一步,开启定时器。

TIM_Cmd(TIM2, ENABLE);

          ​​​​​​​至此,计数器开始计数,PA0 引脚上将输出一个 1kHz,初始占空比为 0% 的PWM信号。


4.3 动态调整占空比(实现呼吸灯)

          ​​​​​​​PWM 的核心价值在于其动态控制能力。在程序运行过程中,动态地修改PWM占空比,可以实现呼吸灯效果、电机调速等关键应用。STM32 标准库为此提供了 TIM_SetComparex 系列函数(x 代表通道号,取值为 1-4),这些函数通过直接操作定时器的 CCR 寄存器来实现功能。

          ​​​​​​​​​​​​​​代码封装示例:

/**
  * @brief  设置 TIM2 通道 1 的占空比
  * @param  Compare: CCR 数值 (0~100)
  */
void PWM_SetCompare1(uint16_t Compare)
{
    TIM_SetCompare1(TIM2, Compare);
}

          ​​​​​​​​​​​​​​在呼吸灯实验中,我们在主循环中不断改变这个Compare值,并调用PWM_SetCompare1,改变PWM占空比,即可以观察到LED亮度的周期性变化。呼吸灯主循环逻辑如下所示:

while (1)
{
    // 渐亮
    for (int i = 0; i <= 100; i++)
    {
        PWM_SetCompare1(i);
        Delay_ms(10);
    }
    // 渐暗
    for (int i = 0; i <= 100; i++)
    {
        PWM_SetCompare1(100 - i);
        Delay_ms(10);
    }
}

拓展:修改PWM频率:除了占空比,如果需要动态修改PWM的频率,可以使用TIM_SetAutoreload函数来修改ARR寄存器的值。例如,TIM_SetAutoreload(TIM2, NewARR_Value);。同样地,为了使新的ARR值平滑生效,建议在配置时基单元时启用ARR的预装载功能。

4.3.1 预装载与影子寄存器机制

          ​​​​​​​​​​​​​​在动态调整 PWM 占空比(如实现呼吸灯或电机调速)时,常频繁调用 TIM_SetComparex 函数。为确保输出波形的连续性,防止在计数周期中途修改CCR而导致波形畸变,STM32 硬件设计采用了预装载(Preload)影子寄存器(Shadow Register)机制。

1. 双缓冲寄存器结构:

          ​​​​​​​​​​​​​​在硬件底层,捕获/比较寄存器(CCR)并非由单一寄存器组成,而是构成了一套双缓冲结构:

  • 预装载寄存器(Preload Register):作为软件层面的操作接口,存放由 TIM_SetComparex 函数写入的新数值。
  • 影子寄存器(Shadow Register):作为硬件层面的执行核心,其数值直接与计数器(CNT)进行实时比较,控制引脚电平。

2. 占空比更新的执行逻辑

          ​​​​​​​​​​​​​​当通过 TIM_OCxPreloadConfig 函数使能预装载功能后,CCR 的更新过程受硬件严格同步:

  • 缓冲存储:在计数周期运行过程中调用 TIM_SetComparex,新数值仅存储于预装载寄存器中。此时影子寄存器仍持有旧值,继续控制当前周期的 PWM 逻辑。
  • 同步加载:新数值的正式生效被延迟至当前计数周期结束。当触发**更新事件(UEV)**时,硬件自动将预装载寄存器中的内容拷贝至影子寄存器。
  • 波形连续性:该机制确保了比较阈值的切换始终发生在周期的起始边界,有效规避了因中途修改而可能产生的非法窄脉冲或电平闪变,保证了 PWM 信号在动态调节过程中的完整性与相位一致性。

4.4 PWM引脚重映射(Remap)

          ​​​​​​​​​​​​​​在实际的项目开发中,GPIO 引脚冲突是常见问题,同一个引脚常被多个外设功能争抢使用。以 TIM2 的通道 1(CH1)为例,其默认输出引脚为 PA0,但 PA0 同时也常被用作串口通信或 ADC 采集引脚。为了解决这种硬件资源冲突,STM32 提供了 引脚重映射(Remap)功能。

          ​​​​​​​​​​​​​​通过 AFIO(Alternate Function I/O,复用功能 I/O)模块,我们可以将外设的特定功能从默认引脚转移到备用引脚上。本节将以 TIM2_CH1 从 PA0 重映射到 PA15 为例,详细拆解重映射的配置逻辑与实现步骤。

4.4.1 映射方案选择与引脚分析

          ​​​​​​​​​​​​​​首先,我们需要查阅芯片手册,确认 TIM2 支持哪些重映射方案。在 STM32F103C8T6 中,TIM2 的 4 个通道支持以下映射方式:

映射方式CH1 引脚CH2 引脚CH3 引脚CH4 引脚
无重映射(默认)PA0PA1PA2PA3
部分重映射 1PA15PB3PA2PA3
部分重映射 2PA0PA1PB10PB11
完全重映射PA15PB3PB10PB11

          ​​​​​​​​​​​​​​从上表可知,若要将 TIM2_CH1 转移到 PA15,必须选择部分重映射 1完全重映射

          ​​​​​​​​​​​​​​接着,我们需要对比源引脚(PA0)与目标引脚(PA15)的硬件属性:

引脚号引脚名称类型I/O 电平

   主功能

(复位后默认)

默认复用功能重定义功能 (Remap)
10PA0-WKUPI/O-PA0 (普通 IO)

WKUP、

USART2_CTS 、

ADC12_IN0、TIM2_CH1_ETR

-
38PA15I/OFTJTDI (调试接口)-

TIM2_CH1_ETR

PA15、

SPI1_NSS

4.4.2 资源冲突深度解析:PA15 与 JTAG

          ​​​​​​​​​​​​​​根据引脚属性表,PA15 存在一个极易踩坑的关键问题:它与 JTAG 调试接口存在功能冲突。

          ​​​​​​​​​​​​​​在 STM32 复位后,PA15 的默认主功能并非普通 GPIO,而是 JTAG 调试接口的 JTDI 信号。在硬件电路上,调试接口的优先级高于普通复用外设。若未经特殊配置,直接将 PA15 初始化为 PWM 输出通道,引脚电平将受控于调试模块,PWM 波形无法正常输出。

          ​​​​​​​​​​​​​​为释放 PA15 的控制权,必须调整调试端口的配置。核心操作准则如下:

  • 禁用 JTAG 功能:通过配置 AFIO 寄存器,关闭 JTAG 调试接口,释放 PA15、PB3、PB4 等引脚。
  • 保留 SWD 功能:在禁用 JTAG 的同时,务必确保 SWD(Serial Wire Debug)接口(PA13/JTMS, PA14/JTCK)保持使能状态,以保证后续程序下载与调试不受影响。

4.4.3 重映射配置步骤

          ​​​​​​​​​​​​​​基于以上分析,配置引脚重映射需要比标准 PWM 初始化多出三个关键步骤:

(1)开启 AFIO 时钟

          ​​​​​​​​​​​​​​重映射功能由 AFIO 模块管理。在配置任何重映射寄存器之前,必须先使能其时钟。

// 开启 AFIO 时钟(重映射的前提条件)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

(2)配置引脚重映射方式

          ​​​​​​​​​​​​​​调用库函数选择对应的重映射模式。

// 选择 TIM2 的部分重映射 1 方式 (CH1 从 PA0 转移到 PA15)
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);

(3)处理调试端口复用冲突

          ​​​​​​​​​​​​​​精确禁用 JTAG 释放目标引脚。

// 禁用 JTAG 功能,保留 SWD 功能,释放 PA15 为普通 IO
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

4.4.4 完整代码示例

          ​​​​​​​​​​​​​​结合标准 PWM 的初始化流程,将 TIM2_CH1 重映射到 PA15 的完整代码如下:

void PWM_Remap_Init(void)
{
    /* 1. 开启时钟 (增加 AFIO 时钟) */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // ❗重映射必须开启 AFIO
    
    /* 2. 配置重映射 & 解除调试端口占用 */
    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);     // 部分重映射1: CH1 -> PA15
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);  // ❗关闭 JTAG,保留 SWD,释放 PA15
    
    /* 3. 配置 GPIO (注意改为 PA15) */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;       // 目标引脚改为 PA15
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 必须为复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    /* 4. 时基单元与输出比较配置 (与普通配置完全一致) */
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    // ... [省略常规的 PSC、ARR 配置代码] ...
    
    TIM_OCInitTypeDef TIM_OCInitStructure;
    // ... [省略常规的 PWM 模式、CCR 配置代码] ...
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);         // 依然使用通道 1 的初始化函数
    
    /* 5. 启动定时器 */
    TIM_Cmd(TIM2, ENABLE);
}

4.4.5 配置要点与排错指南

          ​​​​​​​​​​​​​​重映射过程的细节较多,稍有不慎即会导致配置失效。以下是核心步骤的总结与排错清单:

步骤关键操作代码核心目的与注意事项
1. AFIO 时钟RCC_APB2PeriphClockCmd(...,AFIO,ENABLE)必须开启,否则后续重映射指令无效。
2. 重映射方式GPIO_PartialRemap1_TIM2根据需求查表选择,确保映射逻辑正确。
3. 调试端口释放GPIO_Remap_SWJ_JTAGDisable禁用 JTAG 释放 PA15,保留 SWD 保证烧录
4. GPIO 初始化改为 GPIO_Pin_15引脚号必须与重映射后的物理引脚对应。
5. 通道配置TIM_OC1Init无论引脚如何变化,逻辑上仍属于 TIM2 的 CH1。

警告:谨防芯片“变砖”

          ​​​​​​​​​​​​​​在处理调试端口释放时,千万不要使用 GPIO_Remap_SWJ_Disable 参数。该参数会同时彻底禁用 SWD 和 JTAG 接口。一旦执行,后续将无法通过 ST-Link 或 J-Link 下载程序(俗称锁死变砖),只能通过 BOOT0 切换启动模式使用串口 ISP 工具强行擦除芯片来恢复。


5 相关元器件简介

5.1 SG90 9g 舵机(伺服电机)

          ​​​​​​​​​​​​​​在机器人开发、航模控制及各类自动化项目中,SG90 9g 舵机是一种应用极广的小型执行机构。无论是机械臂关节的运动定位,还是遥控模型的转向控制,均依赖于这种精密组件来实现。实物如图所示:

5.1.1 简介

          ​​​​​​​​​​​​​​舵机(Servo Motor),严格意义上称为位置伺服电机。与通电后持续旋转的常规直流电机不同,舵机的核心特性在于精确的角度定位。它的输出轴通常被限制在一个固定的物理角度范围内(例如 0°~180°),并能够根据接收到的控制信号,精准旋转并悬停在范围内的任意指定角度上。

          ​​​​​​​​​​​​​​从硬件接口上看,SG90 通常引出三根线,其功能定义如下表所示:

线色(常见)接口定义连接说明
棕色GND接系统电源负极(必须与控制板共地)
红色VCC接电源正极(SG90 通常需使用 5V 独立供电)
橙色Signal信号线,接收来自单片机的 PWM 控制信号

5.1.2 内部闭环控制原理

          ​​​​​​​​​​​​​​舵机实现精准角度锁定的基础,在于其内部构建的微型闭环控制系统。SG90 的硬件架构主要由微型直流电机、减速齿轮组、控制芯片及电位器(位置传感器)构成。如图所示:

          ​​​​​​​​​​​​​​其完整控制环路如下:

  1. 指令接收:单片机通过信号线输入特定脉宽的 PWM 信号,该信号脉宽代表目标预期角度。
  2. 误差计算:控制芯片内部的基准电路将接收到的 PWM 信号转化为目标参考电压;同时,实时读取与输出轴同轴转动的电位器电压,该电压代表当前实际角度。
  3. 驱动执行:控制芯片对目标电压与实际电压进行比较运算,产生电压差。电压差的正负号决定电机的旋转方向,电压差的大小影响旋转速度。
  4. 稳定定位:电机旋转时,通过减速齿轮组带动输出轴和电位器同步转动。当实际角度逼近目标角度时,电压差趋于零,电机随之停转,输出轴即稳定保持在指定位置。

5.1.3 PWM 控制协议与角度映射

          ​​​​​​​​​​​​​​为了实现对舵机的精确控制,必须向其信号线输入符合特定时序规范的 PWM(脉冲宽度调制)信号。对于 SG90,其标准控制协议需满足以下要求:

  • 信号周期:20ms(对应 PWM 频率为 50Hz)。
  • 有效脉宽:高电平的持续时间被严格限制在 0.5ms~2.5ms 之间。

          ​​​​​​​​​​​​​​此微秒级的高电平脉宽,与舵机的旋转角度呈现严格的线性映射关系。以标准 180° 舵机为例,其对应关系如下:

高电平持续时间对应角度(0°~180° 标定)对应角度(-90°~+90° 标定)
0.5ms-90°
1.0ms45°-45°
1.5ms90° (中位)
2.0ms135°+45°
2.5ms180°+90°

          ​​​​​​​​​​​​​​例如:高电平持续时间1.0ms + 标准 180° 舵机。

注意:除了180°舵机外,还有360°连续旋转舵机。此类舵机移除了内部的电位器反馈机制,无法实现角度定位。其 PWM 信号(1.5ms 此时为停止阈值)仅能控制电机的旋转方向和速度。在系统设计与采购时需严格区分。

5.1.4 基于 STM32 的 PWM 参数计算

          ​​​​​​​​​​​​​​在明确控制协议后,需将理论参数转化为微控制器的硬件寄存器配置。以主频为 72MHz 的 STM32 为例,需合理配置定时器的预分频器(PSC)和自动重装载值(ARR),以生成周期为 20ms (50Hz)的PWM信号。

1. 确定 ARR 与 PSC :

          ​​​​​​​​​​​​​​为保证角度调节的平滑度,需提供较高的计数分辨率。设定 ARR 为 19999(即计数 20000 次构成一个完整周期):

$T = \frac{(PSC+1) \times (ARR+1)}{72,000,000} = 0.02\text{s}$

          ​​​​​​​​​​​​​​将 ARR = 19999 代入上述公式,解得:

$PSC+1 = 72 \Rightarrow \mathbf{PSC = 71}$

2. 计算 CCR:

          ​​​​​​​​​​​​​​在上述配置下,定时器单次计数耗时为 1us,分辨率为 0.005%。随后根据高电平要求的持续时间,计算捕获/比较寄存器(CCR)的配置值值:

  • 0.5ms(起点):

$CCR = 20000 \times \frac{0.5\text{ms}}{20\text{ms}} = \mathbf{500}$

  • 2.5ms(终点):

$CCR = 20000 \times \frac{2.5\text{ms}}{20\text{ms}} = \mathbf{2500}$

          结论:在代码初始化配置 PSC=71,ARR=19999 后,通过调用 TIM_SetCompareX(TIMx, CCR) 函数,使 CCR 的值在 500~2500 之间线性变化,即可实现舵机 0°~180° 的平滑与精确控制。​​​​​​​​​​​​​​

5.1.5 角度与 CCR 值的线性转换逻辑

          为了实现对舵机输出轴角度的精确控制,必须在目标角度 Angle 与定时器捕获/比较寄存器数值 CCR 之间建立明确的数学映射关系。本节将说明驱动函数中舵机角度设置接口的角度-CCR线性转换原理。

/**
  * 函    数:舵机设置角度
  * 参    数:Angle 要设置的舵机角度,范围:0~180
  * 返 回 值:无
  */
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle / 180 * 2000 + 500);	//设置占空比
												//将角度线性变换,对应到舵机要求的占空比范围上
}

          针对 SG90 舵机,控制协议规定 0.5ms 至 2.5ms 的脉宽对应 0° 至 180° 的转角。在定时器配置为 20ms 周期、计数分辨率为 1us(即 ARR=19999)的条件下,对应的 CCR 取值范围为 500 至 2500。

          该映射关系表现为标准的一元一次线性方程(Linear Equation):y = Kx + b。设定自变量为目标角度 x(Angle),因变量为寄存器值 y(CCR),其推导过程如下:

1. 确定坐标点:

          已知该线性函数经过以下两个边界坐标点:

  • 起点:(x1, y1) = (0, 500)
  • 终点:(x2, y2) = (180, 2500)

2. 求解斜率 k:

          斜率表示单位角度变化所引起的 CCR 数值增量:

$k = \frac{y_2 - y_1}{x_2 - x_1} = \frac{2500 - 500}{180 - 0} = \frac{2000}{180}$

3. 求解截距 b:

          将起点坐标 (0, 500) 代入线性方程 y = kx + b:

$500 = \frac{2000}{180} \times 0 + b \implies b = 500$

4. 建立最终映射方程:

$y = \frac{2000}{180} \cdot x + 500$

5. 代码实现转换:

          在 Servo_SetAngle 函数中,通过表达式 Angle / 180 * 2000 + 500 直接计算目标 CCR 值。该算法确保了在 [0, 180] 连续角度区间内,输出脉宽能够平滑地在 [0.5ms, 2.5ms] 之间线性切换,从而实现伺服系统的精准定位。

5.1.6 进阶探讨:舵机抖动原因与多路控制策略

          在实际应用(如多足机器人系统的开发)中,常会遇到舵机抖动的问题。 当供电电压稳定且负载在许可范围内时,此类现象的产生主要源于PWM 控制信号的微小波动。舵机的死区通常在微秒级别(约 2us 左右),若单片机产生的脉宽发生微量抖动,舵机的闭环系统即会不断校正位置,产生高频震颤。

          高精度控制策略对比:

  • 现代方案(硬件 PWM 生成):如上文所述的 STM32 平台,利用片上定时器的硬件比较输出(OC)功能直接生成 PWM。该方法完全依赖底层硬件寄存器运行,不占用 CPU 资源,输出脉宽精度极高,是消除舵机抖动的最佳设计实践。

  • 传统/妥协方案(软件定时器中断模拟):在早期的低算力微控制器(如 51 单片机)或硬件 PWM 通道受限的系统中,开发者常利用定时器中断(设定 20ms 周期,中断服务程序中翻转 IO 电平并通过 delay 控制高电平持续时间)或外挂外部计数器芯片(如 8253)来模拟 PWM。尽管该方法能够在引脚资源匮乏时实现多路舵机控制,但受限于软件中断响应固有的时间不确定性(即抖动,Jitter),极易引起脉宽误差积累,进而导致舵机微抖。

          因此,在设计具有高精度伺服需求的系统架构时,优先配置并分配硬件定时器的 PWM 通道,是确保系统伺服稳定性的核心原则。


5.2 130电机马达

          130型直流电机是一种广泛应用于微型机电系统、消费电子及小型移动机器人领域的执行机构。因其结构精简、控制逻辑直观且具备较高的转速功率比,该型号电机常作为实验平台及工业原型开发中的基础动力单元。实物如图所示:

5.2.1 物理架构与电磁转换机理

          130直流电机属于永久磁铁直流有刷电机(PMDC)。其运行遵循安培力定律:当外部直流电压施加于电机引脚时,电流通过内部换向器进入转子绕组。由于转子处于定子永久磁场中,受磁场力作用产生转矩,带动输出轴旋转。

          针对直流电机的运动状态控制,通常分为速度调节与方向切换两个维度:

  • 速度调节(Speed Regulation)

          电机的旋转速度与其引脚两端的平均输入电压成正比。在嵌入式开发中,基于脉冲宽度调制(PWM)技术,通过改变信号的占空比(Duty Cycle)来实现等效平均电压的调节。

$V_{avg} = V_{cc} \times \text{Duty}$

          其中,$V_{avg}$ 为等效输出电压,$V_{cc}$ 为电源电压。

  • 方向控制(Direction Control)

          电机的转向取决于输入电流的极性。改变施加于引脚两端电压的正负极性,即可通过改变转子磁场方向实现输出轴的正向或反向旋转。

5.2.2 机电特性参数分析

          在进行电路驱动设计与系统稳定性评估时,需严格参考 130 型电机的电学参数。以下为典型实验环境下该类电机的额定参数表:

参数名称典型取值范围物理意义与工程影响
空载转速11000 ~ 15000 rpm(转/分钟)电机在无负载状态下的最高转速
空载电流60 ~ 100 mA克服内部摩擦与阻力所需的维持电流
启动/堵转电流0.7 ~ 1.2 A电机启动瞬间或负载过重停止时的峰值电流

          堵转电流(Stall Current)是系统设计的核心约束指标。由于电机在静止瞬间或发生堵转时,转子无法切割磁感线产生反电动势(Back-EMF),此时回路阻抗仅由微小的线圈电阻组成,根据欧姆定律,瞬时电流会激增。这一特性决定了单片机的 GPIO 引脚无法直接驱动电机,且电源系统需具备足够的瞬态响应能力。

5.2.3 硬件接口约束与保护逻辑

          直流电机作为感性功率器件,其电气特性对逻辑电路具备较强的干扰性与破坏性,因此在硬件接口设计中必须遵循以下准则:

5.2.3.1 功率放大与驱动隔离

          单片机(如 STM32)的 GPIO 典型输出电流通常被限制在 20mA 左右,远低于130 电机的工作电流需求。若强行驱动,将导致引脚过载损坏。工程实践中必须引入集成化功率驱动芯片(如 TB6612FNGL298N)。驱动芯片通过 H 桥电路结构实现大电流的输出控制,并完成弱电信号与强电功率的物理/电气隔离。

5.2.3.2 反电动势及其抑制

          电机本质上是一个感性负载,根据电磁感应定律,当电机断电或快速换向时,线圈电流的突变($\frac{di}{dt}$)会产生极高的反向瞬变电压:

$V = L \frac{di}{dt}$

          此尖峰电压(Spike Voltage)若反馈至逻辑电路或驱动电路,可能导致半导体元器件发生击穿损坏。为保护系统,必须在驱动电路中配置续流二极管(Flyback Diode)。在 H 桥驱动芯片内部,通常已集成了此类保护二极管,用于在关断瞬间提供泄放路径,消耗感性电势产生的多余能量。

5.2.3.3 电源噪声抑制

          电机运行过程中,碳刷换向会产生高频电磁干扰(EMI)。为降低噪声对微控制器系统的影响,通常建议在电机引脚两端并联一颗 0.1uF 的陶瓷电容,并在电机电源输入端配置大容量电解电容,以平滑电流波动并吸收高频干扰信号。


5.3 TB6612FNG电机驱动模块

          在嵌入式机电控制系统中,微控制器(MCU)的 GPIO 引脚因电流驱动能力受限,无法直接驱动直流电机等感性负载。TB6612FNG 是一款双通道 MOSFET 型 H 桥驱动芯片,凭借其高效率、低发热量及小型化封装的特性,已成为替代传统 L298N 驱动器的主流工业方案,尤其适用于 130 型等微型直流电机的精密控制。

5.3.1 模块架构与技术优势

          TB6612FNG 内部集成了两套完全独立的 H 桥(H-Bridge) 驱动电路。H 桥是一种典型的电路拓扑,通过四个受控开关管的导通与截止组合,实现对感性负载电流方向及大小的控制。其结构如图所示:

          相比于基于双极性晶体管(BJT)设计的 L298N,TB6612FNG 采用 功率 MOSFET 作为输出级。其核心技术优势表现在:

  • 低导通电阻:由于 MOSFET 的导通压降远低于 BJT,芯片在额定电流范围内功率损耗极小,转换效率更高,基本无需额外安装散热片。
  • 高开关频率:支持高达 100kHz 的 PWM 输入频率,能够实现平滑的转速控制并有效抑制电机运行时的电磁噪声。
  • 多重保护机制:芯片内置过热保护(Thermal Shutdown)及低电压检测电路,提升了系统的可靠性。

5.3.2 硬件接口与引脚定义

          TB6612FNG 模块的引脚分布可分为电源接口、逻辑控制接口与功率输出接口三部分。具体定义如下表所示:

引脚分类引脚名称功能描述工程连接说明
电源接口VM电机驱动电源正极连接外部电源(如电池、稳压模块)的正极,为电机旋转提供能量。电压范围一般为2.5V ~ 12V。
VCC逻辑供电正极连接 MCU 的 3.3V 或 5V 输出,为内部逻辑电路供电。
GND公共参考地连接电源负极,必须与单片机系统实现共地。
逻辑输入AIN1 / AIN2A 路方向控制连接 MCU 的 GPIO 引脚。通过电平组合控制 A 路电机转向。
BIN1 / BIN2B 路方向控制功能同 A 路,用于控制 B 路电机。
PWMA / PWMB调速控制引脚连接 MCU 的 PWM 输出引脚。通过调节占空比改变平均电压,实现调速。
STBY待机控制高电平时芯片正常工作;低电平时进入低功耗模式,输出关闭。
功率输出AO1 / AO2A 路功率输出直接连接至 A 路电机的两个输入端。
BO1 / BO2B 路功率输出直接连接至 B 路电机的两个输入端。

5.3.3 控制逻辑真值表与状态解析

          TB6612FNG 的逻辑控制具有高度的确定性。以 A 通道为例,其输出状态由 STBY、AIN1、AIN2 以及 PWMA 四个信号共同决定,如图所示:

          TB6612FNG 控制逻辑真值表:

STBYAIN1AIN2PWMA输出状态 (AO1, AO2)电机运动状态解析
0XXX高阻态 (Hi-Z)待机模式:所有输出关闭,进入节能状态。
110PWM$V_{avg} \propto \text{Duty}$正转:转速由 PWM 信号的占空比决定。
101PWM$V_{avg} \propto \text{Duty}$反转:转速由 PWM 信号的占空比决定。
111X低电平 (Low)短接制动(Brake):电机绕组短路,产生反电动势实现强制刹车。
100X高阻态 (Hi-Z)停止:电机依靠摩擦力自由滑行至停止。

$V_{avg} \propto \text{Duty}$ 表达的是等效平均电压与占空比之间的正比关系。

  • $V_{avg}$:平均电压(Average Voltage)。指在一个周期内,脉冲信号折算后的等效直流电压。
  • $\propto$:正比符号。表示左侧变量随右侧变量的线性增加而增加。
  • $\text{Duty}$:占空比(Duty Cycle)。指在一个脉冲周期内,高电平(有效电平)持续时间占整个周期时间的百分比,数值范围通常为 $0\% \sim 100\%$(或 $0 \sim 1$)。

5.3.4 实现结论与工程建议

          TB6612FNG 通过逻辑信号与功率输出的解耦,实现了对直流电机的精密驱动。在实际工程部署中,应注意以下几点:

  • 待机管脚管理:STBY 引脚不可悬空。若无需低功耗控制,可直接将其连接至 VCC;若需受控,则需连接 MCU 引脚并在初始化时置高。
  • PWM 频率选择:虽然芯片支持 100kHz,但过高的频率会增加开关损耗。通常建议将 PWM 频率设定在 10kHz ~ 20kHz 之间,以平衡控制精度与转换效率。
  • 电源去耦:在 VM 接口附近应放置大容量电解电容(如 100uF ~ 470uF),以吸收电机换向产生的感性浪涌电流,确保系统电压的稳定性。

          通过对 TB6612FNG 逻辑与电气特性的准确配置,开发者能够实现对直流电机在方向、速度及制动性能上的全方位控制。


6 本章节实验

6.1 PWM驱动LED呼吸灯

6.1.1 实验目标

          本实验旨在通过 STM32 定时器的输出比较(Output Compare)功能生成脉冲宽度调制(PWM)波形,驱动 LED 实现呼吸灯效果。实验的核心技术要求包括:

  • 硬件资源配置:掌握 STM32 定时器时基单元、输出比较单元及 GPIO 复用功能的初始化流程。
  • 参数计算与映射:理解 PWM 频率、占空比与硬件寄存器(ARR、PSC、CCR)之间的数学映射关系。
  • 动态占空比调节:实现在程序运行中通过动态修改 CCR 寄存器值来控制输出等效电压。
  • 引脚重映射应用:掌握 AFIO 时钟配置及引脚重映射技术,实现输出通道在不同 GPIO 引脚间的灵活切换。

6.1.2 硬件设计

6.1.3 软件设计

          本实验的软件逻辑主要分为 PWM 模块初始化占空比动态控制循环两大部分。

1. PWM 模块初始化配置

          初始化流程严格遵循硬件时钟树与外设配置协议:

  • 时钟使能:开启 APB1 总线上的 TIM2 时钟以及 APB2 总线上的 GPIOA 时钟。若涉及引脚重映射,需额外开启 AFIO 复用功能时钟。
  • GPIO 模式设置:输出引脚需配置为复用推挽输出(GPIO_Mode_AF_PP),以确保引脚控制权交由定时器外设而非 CPU 数据寄存器。
  • 时基单元初始化:通过设定预分频器(PSC)与自动重装载寄存器(ARR)来确定 PWM 的基准频率。
  • 输出比较单元配置:设定为 PWM 模式 1,配置输出极性为高。初始捕获/比较寄存器(CCR)设为 0,即初始占空比为 0%。

2. 主程序控制算法

          主循环通过双重循环结构实现 LED 亮度的平滑过渡。程序不断修改 $CCR$ 寄存器的值,改变 PWM 的高电平持续时间,从而改变 LED 的平均工作电流。每个步进周期辅以微秒级延时,以匹配人眼的视觉暂留特性,最终呈现周期性的呼吸视觉效果。

          具体代码如下:

          main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t i;			//定义for循环的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	
	while (1)
	{
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(i);			//依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
			Delay_ms(10);				//延时10ms
		}
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(100 - i);	//依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
			Delay_ms(10);				//延时10ms
		}
	}
}

          PWM.c文件:

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO重映射*/
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式		
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值
																	//则最好执行此函数,给结构体所有成员都赋一个默认值
																	//避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}

6.1.4 实验现象

          将程序下载至开发板后,连接在PA0(或重映射后的PA15)上的LED会呈现呼吸灯效果:亮度从暗逐渐变亮,再逐渐变暗,如此循环反复。使用示波器测量对应引脚,可观察到频率为1kHz的PWM波形,其占空比在0%至100%之间周期性变化。


6.2 PWM驱动舵机

6.2.1 实验目标

          本实验旨在通过STM32的PWM输出功能驱动舵机,实现按键控制舵机旋转至指定角度。通过本实验,您将学习到:

  • 掌握如何根据舵机的控制要求(周期20ms,高电平0.5ms~2.5ms对应-90°~+90°或0°~180°)配置PWM参数(频率、占空比范围)。
  • 学习将角度值线性映射到PWM占空比(CCR值)的方法,使舵机能够精确转动到目标角度。
  • 理解多模块协同编程思想:将PWM底层驱动封装为独立模块,上层应用模块(舵机控制)调用底层接口,实现代码分层与复用。
  • 掌握按键输入检测与消抖处理,结合OLED显示实现人机交互。

6.2.2 硬件设计

6.2.3 软件设计

          本实验的软件结构分为三层:底层硬件驱动(PWM、按键)、中间层舵机控制模块、上层应用逻辑(主循环)。各模块分工明确,便于维护和扩展。

1. PWM模块配置(PWM.c)和 按键模块(Key.c)

          PWM模块负责输出符合舵机要求的波形,使用TIM2的通道2,引脚为PA1。关键配置如下:

  • 时钟使能:开启TIM2和GPIOA时钟。
  • GPIO初始化:将PA1设置为复用推挽输出(GPIO_Mode_AF_PP),由TIM2控制引脚电平。
  • 时基单元
  • 预分频器PSC = 72 - 1,自动重装值ARR = 20000 - 1。
  • 计算得PWM频率 = 72MHz / (72 × 20000) = 50Hz,周期20ms,符合舵机控制要求。
  • 计数器模式为向上计数。
  • 输出比较
  • 选择PWM模式1(TIM_OCMode_PWM1),极性高有效(TIM_OCPolarity_High)。
  • 使能输出,初始CCR值设为0。
  • 使用TIM_OC2Init配置通道2。
  • 使能定时器TIM_Cmd启动TIM2。

          同时提供PWM_SetCompare2函数,用于运行时修改CCR2的值,从而改变占空比。

          按键模块则使用PB1和PB11两个引脚,配置为上拉输入。Key_GetNum函数通过查询方式检测按键按下,包含消抖和松手检测,返回键码(1或2)。本实验中仅使用按键1(PB1)。

2. 舵机控制模块(Servo.c)

          舵机模块封装了PWM底层,对外提供简单易用的角度设置接口:

  • Servo_Init:调用PWM_Init初始化PWM。
  • Servo_SetAngle:接收角度参数(0~180°),通过线性映射公式 CCR = Angle / 180 × 2000 + 500 计算出对应的CCR值(500~2500),再调用PWM_SetCompare2设置占空比。该公式实现了角度与脉宽的线性对应:0°→500(0.5ms),180°→2500(2.5ms)。

3. 主程序逻辑(main.c)

  1. 初始化:依次初始化OLED、舵机、按键模块。OLED用于显示当前角度。

  2. 显示静态字符串:在OLED第一行显示“Angle:”提示。

  3. 主循环
  • 调用Key_GetNum获取按键状态。
  • 若按键1按下,角度变量Angle自增30°,超过180°则归零。
  • 调用Servo_SetAngle将当前角度写入舵机,驱动舵机旋转。
  • 更新OLED显示角度值,便于观察。

          程序通过简单的按键触发和角度映射,实现了舵机的步进控制,展示了PWM在电机控制中的典型应用。

          具体代码如下:

          main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum;			//定义用于接收键码的变量
float Angle;			//定义角度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Servo_Init();		//舵机初始化
	Key_Init();			//按键初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Angle:");	//1行1列显示字符串Angle:
	
	while (1)
	{
		KeyNum = Key_GetNum();			//获取按键键码
		if (KeyNum == 1)				//按键1按下
		{
			Angle += 30;				//角度变量自增30
			if (Angle > 180)			//角度变量超过180后
			{
				Angle = 0;				//角度变量归零
			}
		}
		Servo_SetAngle(Angle);			//设置舵机的角度为角度变量
		OLED_ShowNum(1, 7, Angle, 3);	//OLED显示角度变量
	}
}

          PWM.c文件:

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/ 
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);		//设置CCR2的值
}

          Servo.c文件:

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/**
  * 函    数:舵机初始化
  * 参    数:无
  * 返 回 值:无
  */
void Servo_Init(void)
{
	PWM_Init();									//初始化舵机的底层PWM
}

/**
  * 函    数:舵机设置角度
  * 参    数:Angle 要设置的舵机角度,范围:0~180
  * 返 回 值:无
  */
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle / 180 * 2000 + 500);	//设置占空比
												//将角度线性变换,对应到舵机要求的占空比范围上
}

          Key.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * 函    数:按键初始化
  * 参    数:无
  * 返 回 值:无
  */
void Key_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1和PB11引脚初始化为上拉输入
}

/**
  * 函    数:按键获取键码
  * 参    数:无
  * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
  * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 1;												//置键码为1
	}
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 2;												//置键码为2
	}
	
	return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

6.2.4 实验现象

          下载程序后,OLED屏幕显示“Angle:0”。按下连接在PB1上的按键,舵机输出轴会旋转30°,OLED显示的角度值同步增加30°。每按一次按键,角度增加30°,依次为30°、60°、90°、120°、150°、180°,再次按键角度归零,舵机回到0°位置。

          使用示波器测量PA1引脚,可观察到周期为20ms的PWM波形,高电平时间随角度变化在0.5ms~2.5ms之间步进。


6.3 PWM驱动直流电机

6.3.1 实验目标

          本实验旨在通过STM32的PWM输出功能配合电机驱动芯片(TB6612)控制直流电机的转速和方向,实现按键步进调速与正反转切换。通过本实验,将学习到:

  • 掌握直流电机驱动的基本原理:通过H桥电路控制电机正反转,通过PWM占空比调节转速。
  • 学习如何将PWM模块与GPIO方向控制相结合,封装出简洁的电机控制接口(速度值-100~100)。
  • 理解PWM频率对电机运行的影响,学会选择合适频率以避免可闻噪音(本实验选择20kHz)。
  • 进一步熟悉模块化编程思想,将底层PWM、按键、OLED与电机控制逻辑分层实现。

6.3.2 硬件设计

6.3.3 软件设计

          本实验软件由底层PWM驱动、电机控制模块、按键模块和主程序组成。电机模块封装了方向控制和速度设置,对外提供直观的带符号速度接口。

1. PWM模块配置(PWM.c)

          PWM模块使用TIM2的通道3,引脚为PA2,输出20kHz的PWM波形。关键配置如下:

  • 时钟使能:开启TIM2和GPIOA时钟。
  • GPIO初始化:PA2设置为复用推挽输出(GPIO_Mode_AF_PP)。
  • 时基单元:预分频器PSC = 36 - 1,自动重装值ARR = 100 - 1,计数器向上计数。PWM频率 = 72MHz / (36 × 100) = 20kHz,高于人耳听觉上限,避免电机产生可闻噪音。
  • 输出比较:配置为PWM模式1,极性高有效,初始CCR=0,使能输出,使用TIM_OC3Init配置通道3。
  • 使能定时器:启动TIM2。

          同时提供PWM_SetCompare3函数,用于运行时修改CCR3值,控制占空比(0~100)。

2. 电机控制模块(Motor.c)

          电机模块管理两个部分:直流电机初始化和设置速度。

  • 直流电机初始化

  • 开启GPIOA时钟。
  • 将PA4、PA5配置为推挽输出(GPIO_Mode_Out_PP),用于控制TB6612的AIN1、AIN2引脚。
  • 调用PWM_Init初始化PWM。
  • 速度设置函数 Motor_SetSpeed(int8_t Speed)
  • 输入参数Speed范围-100~100,正值表示正转,负值表示反转。
  • 根据Speed符号设置方向引脚:正转:PA4高电平,PA5低电平。反转:PA4低电平,PA5高电平。
  • 调用PWM_SetCompare3设置PWM占空比:正转时直接传入Speed,反转时传入-Speed(因为PWM值需为正数)。
  • 这种设计实现了用单一有符号变量同时控制方向和转速。

3. 按键模块(Key.c)

          与舵机实验相同,使用PB1和PB11两个上拉输入按键。Key_GetNum返回键码,本实验仅使用按键1(PB1)。

4. 主程序逻辑(main.c)

  1. 初始化:OLED、电机模块、按键模块依次初始化。
  2. 显示静态字符串:在OLED第一行显示“Speed:”。
  3. 主循环:
  • 调用Key_GetNum获取按键状态。
  • 若按键1按下,速度变量Speed增加20;若超过100,则跳转至-100(实现正反转循环切换)。
  • 调用Motor_SetSpeed将当前速度写入电机。
  • 更新OLED显示当前速度值(带符号,三位数)。

          程序通过按键触发速度步进,直观展示了PWM调速和方向控制的效果。

          具体代码如下:

          main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"

uint8_t KeyNum;		//定义用于接收按键键码的变量
int8_t Speed;		//定义速度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Motor_Init();		//直流电机初始化
	Key_Init();			//按键初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
	
	while (1)
	{
		KeyNum = Key_GetNum();				//获取按键键码
		if (KeyNum == 1)					//按键1按下
		{
			Speed += 20;					//速度变量自增20
			if (Speed > 100)				//速度变量超过100后
			{
				Speed = -100;				//速度变量变为-100
											//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位
											//若出现了此现象,则应避免使用这样的操作
			}
		}
		Motor_SetSpeed(Speed);				//设置直流电机的速度为速度变量
		OLED_ShowSignedNum(1, 7, Speed, 3);	//OLED显示速度变量
	}
}

          PWM.c文件:

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA2引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;                 //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;               //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/ 
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC3Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2, Compare);		//设置CCR3的值
}

          Motor.c文件:

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/**
  * 函    数:直流电机初始化
  * 参    数:无
  * 返 回 值:无
  */
void Motor_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA4和PA5引脚初始化为推挽输出	
	
	PWM_Init();													//初始化直流电机的底层PWM
}

/**
  * 函    数:直流电机设置速度
  * 参    数:Speed 要设置的速度,范围:-100~100
  * 返 回 值:无
  */
void Motor_SetSpeed(int8_t Speed)
{
	if (Speed >= 0)							//如果设置正转的速度值
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);	//PA4置高电平
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);	//PA5置低电平,设置方向为正转
		PWM_SetCompare3(Speed);				//PWM设置为速度值
	}
	else									//否则,即设置反转的速度值
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//PA4置低电平
		GPIO_SetBits(GPIOA, GPIO_Pin_5);	//PA5置高电平,设置方向为反转
		PWM_SetCompare3(-Speed);			//PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
	}
}

          Key.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * 函    数:按键初始化
  * 参    数:无
  * 返 回 值:无
  */
void Key_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1和PB11引脚初始化为上拉输入
}

/**
  * 函    数:按键获取键码
  * 参    数:无
  * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
  * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 1;												//置键码为1
	}
	
	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下
	{
		Delay_ms(20);											//延时消抖
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	//等待按键松手
		Delay_ms(20);											//延时消抖
		KeyNum = 2;												//置键码为2
	}
	
	return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

6.3.4 实验现象

          下载程序后,OLED屏幕初始显示"Speed:0",此时电机处于静止状态。

          操作说明:

  1. 每次按下PB1按键,速度值以20为增量逐步增加,依次显示20、40、60、80、100,此时电机正向转速随之提升(正值表示正向旋转,数值大小代表转速快慢);
  2. 再次按键后速度切换为-100,电机立即以最大速度反向旋转(负值表示反向旋转);
  3. 继续按键时,速度值依次变为-80、-60、-40、-20,最终归零停止;
  4. OLED屏幕实时更新当前速度值,电机转向与速度值的正负符号保持一致;

          技术说明:

  • PWM工作频率为20kHz,确保电机运行时无噪声干扰
  • 注:在堵转情况下可能出现轻微啸叫声,此为正常现象
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐