【STM32】江科大学习笔记——TIM定时中断2(输出比较功能)

STM32 PWM输出比较应用详解

该笔记基于B站江科大视频内容结合自己的理解进行编写,如有错误,请指正。视频结合笔记共同食用,效果更佳哦~

TIM(Timer,定时器),是 STM32 中功能最强大、结构最复杂的一个外设,学习内容较多,所以 TIM 部分分为 4 个部分,本文为第 2 部分,围绕 TIM 的输出比较功能进行展开。输出比较这个模块最常见的作用就是产生 PWM 波形,用于驱动电机等设备,所以该部分将学习使用 STM32 输出的 PWM 波形驱动舵机和直流电机。



输出比较简介

输出比较功能主要用来输出 PWM 波形,PWM 波形又是驱动电机的必要条件,如果想用 STM32 实现电机相关项目,比如智能车、机器人等,就需要掌握好这个内容。

输出比较(OC,Output Compare),可以通过比较 CNT(计数器)和 CCR(捕获/比较寄存器)寄存器值的关系,来对输出电平进行置 1、置 0 或翻转的操作,从而输出特定频率和占空比的 PWM 波形。这就是输出比较模块最主要的功能。
CNT 计数自增,给定 CCR 一个值,当 CNT>CCR、CNT<CCR、CNT=CCR 时,输出就会对应的置 1 或 0,实现输出一个电平不断跳变的 PWM 波形。

所有高级定时器和通用定时器均配备 4 个输出比较通道。其中高级定时器的前 3 个通道还支持死区生成和互补输出功能,可以用于驱动三相无刷电机,本章暂时不作为重点内容,主要学习通用定时器的输出比较功能。

PWM 简介

PWM(Pulse Width Modulation,脉冲宽度调制)是一个数字输出信号,由高低电平组成的连续变化电平信号,如下图所示:

在这里插入图片描述

图2-1

PWM 的作用是在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域。简而言之,就是使用 PWM 波形等效地实现一个模拟信号的输出,如下图所示:

在这里插入图片描述

图2-2 PWM 波形与模拟信号的等效关系

当高电平时间占比大于低电平,等效的模拟量偏上;当高电平时间占比小于低电平,等效的模拟量偏下。
LED 呼吸灯实验中,按理说对于数字输出控制 LED,LED 只能有亮灭两种状态,但通过 PWM 却可以实现亮度调节。PWM 让 LED 亮灭交替,当亮灭的频率足够高时,LED 就不会闪烁了,而是呈现出稳定的中间亮度。通过精确控制亮灭的时间比例,就能实现亮度调节,电机调速也是同样原理。这就是 PWM 的基本思想。

需要注意的是,PWM 的应用必须满足一个前提条件:系统需要具备惯性特性。比如 LED,由于余晖和人眼视觉暂留现象,LED 在断电后不会立即熄灭,而是有一定的惯性,需经过一段短暂的时间才会熄灭。同理,电机在断电时也不会瞬间停止,而是会经历一个逐渐减速的过程。这样具有惯性的系统才适用 PWM。

在使用 PWM 时,需要关注以下几个参数:

频率 = 1 高低电平跳变周期时间 T S 频率 = \frac{1}{高低电平跳变周期时间T_S} 频率=高低电平跳变周期时间TS1

PWM 频率越快,等效的模拟信号就越平稳,但性能开销越大。一般来说 PWM 频率在几千赫到几十千赫,这个频率已经足够快了。

占空比 = 高电平时间 T O N T S 占空比 = \frac{高电平时间T_{ON}}{T_S} 占空比=TS高电平时间TON

占空比=50%,表示高低电平时间相等;占空比=20%,表示高电平占 20%,低电平占 80%;占空比=100%,表示一直维持高电平;占空比=0%,表示一直维持低电平。这就是占空比,决定了 PWM 等效的模拟电压大小。占空比越大,等效的模拟电压越趋于高电平,占空比越小,等效的模拟电压越趋于低电平。这个等效关系一般来说是线性的,比如高电平=5V,低电平=0V,则占空比=50%等效中间电压 2.5V,占空比=20%等效 1V。

分辨率 = 占空比变化步距 分辨率 = 占空比变化步距 分辨率=占空比变化步距

比如有的占空比只能是 1%、2%、3% 这样以 1% 的步距跳变,其分辨率就是 1%;如果占空比可以 1.1%、1.2%、1.3% 这样以 0.1% 的步距跳变,其分辨率就是 0.1%。所以分辨率就是占空比变化的精细程度。分辨率的高低根据项目需求决定。如果既要高频率,又要高分辨率,这就对硬件电路要求比较高。要求不高,1% 的分辨率足够。

以上就是本小节的所有内容。

输出比较模块工作原理

通用定时器

B站空降链接


在这里插入图片描述

图3-1 通用定时器输出比较部分电路(高级定时器第 4 输出通道结构基本一致)

上图对应通用定时器以下电路:

在这里插入图片描述

图3-2

图3-1中,当【CNT>CCR1】或者【CNT=CCR1】时,会给【输出模式控制器】传递信号,然后【输出模式控制器】会改变其输出的【oc1ref】(ref 是 reference 的缩写,意为参考信号)的高低电平。

【输出模式控制器】上还有【ETRF】输入,是定时器的一个小功能,一般不用,暂时不需要了解。

接着【oc1ref】可以前往主模式控制器,即可以把该信号映射到主模式的 TRGO 输出上。

在这里插入图片描述

图3-3

不过【oc1ref】的主要去向还是另一条路,前往极性选择电路。

在这里插入图片描述

图3-4

当给寄存器【CC1P】写 0,信号往上走,电平保持不变,输出与输入一致;写 1,信号往下走,经非门取反后输出,实现电平翻转。这就是极性选择。

信号随后进入【输出使能电路】,由该电路决定是否进行输出。若选择输出,信号将被传送至【OC1】引脚,对应图3-2的 CH1 引脚,具体对应的GPIO端口可在引脚定义表中查询。

接下来看下【输出模式控制器】的执行逻辑,如下表所示:

表3-1 输出比较的 8 种模式
模式描述举例
冻结CNT=CCR 时,REF 保持为原状态当遥控车以50%占空比的速度行驶时:
1. 若进入冻结模式后引脚保持高电平,车辆将立即转为全速前进
2. 若引脚变为低电平,车辆将惯性滑行直至完全停止
匹配时置 有效电平CNT=CCR 时,REF 置有效电平不适合输出连续变化的波形
匹配时置 无效电平CNT=CCR 时,REF 置无效电平不适合输出连续变化的波形
匹配时电平翻转CNT=CCR 时,REF 电平翻转当 CCR 设为 0 时,CNT 计数器每次清零都会触发 CNT=CCR 事件,导致输出电平翻转。由于每两次翻转构成一个完整周期,因此高低电平持续时间相等,从而实现 50% 的占空比。需注意,调整定时器更新频率将直接影响 PWM 信号的输出频率。
强制为无效电平忽略 CNT 与 CCR,REF 强制为无效电平遥控信号丢失时,遥控车停止驱动电机。
强制为有效电平忽略 CNT 与 CCR,REF 强制为有效电平无人机传感器检测到高度骤降时紧急全速拉升。
PWM 模式 1向上计数:CNT<CCR 时,REF 置有效电平,CNT≥CCR 时,REF 置无效电平
向下计数:CNT>CCR 时,REF 置无效电平,CNT≤CCR 时,REF 置有效电平
-
PWM 模式 2向上计数:CNT<CCR 时,REF 置无效电平,CNT≥CCR 时,REF 置有效电平
向下计数:CNT>CCR 时,REF 置有效电平,CNT≤CCR 时,REF 置无效电平
-

【输出模式控制器】的模式可以通过配置寄存器【OC1M[2:0]】进行选择。

“PWM 模式 1” 和 “PWM 模式 2” 只是极性上取反的区别,实际只使用 “PWM 模式 1” + “向上计数” 这种模式即可。那这种模式是如何输出频率和占空比都可调的 PWM 波形呢?

在这里插入图片描述

图3-5 PWM 基本结构(PWM 模式 1、向上计数)

上图中,蓝线是【CNT 计数器】的值,黄线是【ARR 自动重装器】的值。蓝线从 0 开始递增,达到黄线数值后自动归零,从而开启新的计数周期。红线是【CCR 捕获/比较器】的值,绿线则显示 PWM 波形的实际输出。
观察得到,蓝线小于红线时(CNT<CCR),绿线处于高电平;蓝线大于红线时(CNT≥CCR),绿线处于低电平。
由此可见,绿线 PWM 的占空比是受红线 CCR 控制,CCR 越大,占空比越大,反之越小。黄线 ARR 越大,绿线 PWM 频率越小,ARR 越小,PWM 频率越大。
由此证明【REF】是一个频率和占空比均可调制的 PWM 波形。最终再经过【极性选择输出使能】输出到【GPIO】。这样就完成 PWM 波形的输出了。

图3-5中可以获得参数:
P W M 频率 F r e q = 计数器时钟 C K _ C N T = C K _ P S C P S C + 1 A R R + 1 = 72 , 000 , 000 99 + 1 = 720 k H z P W M 占空比 D u t y = C C R A R R + 1 = 30 99 + 1 = 30 % P W M 分辨率 R e s o = 1 A R R + 1 = 1 99 + 1 = 1 % \begin{aligned} PWM 频率Freq = 计数器时钟 CK\_CNT &= \frac{\frac{CK\_PSC}{PSC+1}}{ARR+1} \\ &= \frac{72,000,000}{99+1} \\ &= 720kHz \end{aligned} \\ PWM 占空比 Duty = \frac{CCR}{ARR+1} = \frac{30}{99+1}=30\% \\ PWM 分辨率 Reso = \frac{1}{ARR+1} = \frac{1}{99+1}=1\% PWM频率Freq=计数器时钟CK_CNT=ARR+1PSC+1CK_PSC=99+172,000,000=720kHzPWM占空比Duty=ARR+1CCR=99+130=30%PWM分辨率Reso=ARR+11=99+11=1%

PWM 分辨率是指占空比可调节的最小单位(步距)。因此 CCR 的取值范围应该在 [0, ARR+1]。
CCR=ARR+1 时,占空比正好=100%,此时若继续增大 CCR 值也不会改变占空比,没有意义。因此,ARR 值决定了 CCR 的最大取值,ARR 越大则 CCR 调节范围越广,相应的 PWM 分辨率也越高。

高级定时器(可选)

B站空降链接

在这里插入图片描述

图3-6 高级定时器前 3 个通道的输出比较部分电路

虽然视频教程说明这部分电路仅需了解即可,无需掌握,但我仍决定记录下来,以备后续可能的使用需求。

图3-1进行对比可见,高级定时器的输出比较部分仅额外增加了如下电路:

在这里插入图片描述

图3-7

要理解上图所选电路的功能,需结合下图外部电路综合分析:

在这里插入图片描述

图3-8

上图【P-MOS】和【N-MOS】左边是控制极,若【OC1】输入高电平,则【P-MOS】导通,若【OC1】输入低电平,则【P-MOS】断开,【N-MOS】同理。这就是一个基本的推挽电路。

若【P-MOS】导通,【N-MOS】断开,则输出高电平;若【P-MOS】断开,【N-MOS】导通,则输出低电平;若【P-MOS】【N-MOS】都导通,则电源短路,这是不允许的;若【P-MOS】【N-MOS】都断开,则输出就是高阻态。这就是推挽电路的工作流程。

采用两个图3-8所示的推挽电路即可构成H桥电路,实现对直流电机的正反转控制。若增加到三个推挽电路,则可用于驱动三相无刷电机。

若需要使用单片机控制推挽电路,就需要 2 个控制极,且这 2 个控制极需保持互补电平(即电平相反)。理解了推挽电路的这个基本要求后,图3-7中的电路原理就很容易掌握了。

【OC1】【OC1N】是一对互补的输出,在切换【P-MOS】【N-MOS】状态时,若【P-MOS】断开瞬间【N-MOS】导通,由于器件的不理想,可能【P-MOS】未完全断开,而【N-MOS】已经导通,出现了短暂的短路,这种短路会产生额外功率损耗并导致器件发热。
为解决这一问题,设计了死区生成电路。它会在【P-MOS】断开时延迟一段时间后再导通【N-MOS】,【N-MOS】断开时延迟一段时间后再导通【P-MOS】。通过这种时序控制,可确保两个MOS管不会同时导通。

以上就是死区生成和互补输出的内容,了解一下即可。

舵机简介

B站空降链接

舵机是一种根据输入的 PWM 信号占空比来控制输出角度的装置。

在这里插入图片描述

图4-1 SG90 舵机

上图舵机有 3 根输入线,2 根电源线,1 根信号线,PWM 信号就是通过信号线控制舵机角度。

在这里插入图片描述

图4-2 舵机拆解图

从上图可以看出,舵机内部是由【直流电机】驱动的,【舵机内部电板】是电机的控制系统,大概的执行逻辑是,PWM 信号输入【舵机内部电板】后设定目标角度,然后【电位器】检测输出轴的当前角度,若大于目标角度,电机反转,若小于目标角度,电机正转,最终使输出轴固定在指定角度。

输入 PWM 信号的要求:

  • 周期为 20ms,即 1 0.02 = 50 H z \frac{1}{0.02} = 50Hz 0.021=50Hz
  • 高电平宽度为 0.5ms 到 2.5ms,即占空比范围,占空比对应的角度如下图:
    在这里插入图片描述
图4-3 SG90 舵机占空比对应的角度

舵机中 PWM 波形其实是当作一个通信协议来使用的,和前文提到的用 PWM 波形等效模拟输出关系不大。

在这里插入图片描述

图4-4

在这里插入图片描述

图4-5

电机通常属于大功率设备,因此需要配备相应的大功率驱动电源。建议采用图4-5所示的独立【电机电源】供电方案。若无法单独供电,则需确保电源功率满足要求。

当使用独立供电时,需注意:

  • 【电机电源】的负极必须与STM32共地
  • 【电机电源】的正极连接至【5V】供电引脚。由于该舵机内置驱动电路,可直接与STM32引脚相连。

以上就是舵机的所有内容。

直流电机及驱动简介

B站空降链接

在这里插入图片描述

图5-1 130 直流电机

直流电机是一种将电能转换为机械能的装置,有 2 个电极,当电极正接时,电机正转,当电极反接时,电机反转。

直流电机属于大功率器件,无法通过 GPIO 口直接驱动,需要配合电机驱动电路来操作。图5-1中的直流电机就是一个单独的电机,没有内置驱动电路,因此需要外接驱动电路进行控制。市场上有多种成熟的驱动方案可供选择,包括 TB6612、DRV8833、L9110 和 L298N 等常见的驱动芯片。此外,也可采用分离 MOS 管搭建驱动电路,这种方式不仅能实现更大的驱动功率,同时也为用户提供了自主搭建电路的选择。

在这里插入图片描述

图5-2 TB6612 直流电机驱动电路

TB6612 是一款采用双路H桥结构的直流电机驱动芯片,可同时驱动两个直流电机并精确控制其转速和转向。得益于 H 桥设计中每路配备的 4 个开关管,该芯片能够实现电机的正反转控制。相比之下,ULN2003 等单开关管结构的驱动芯片仅能实现单向电机控制。

在这里插入图片描述

图5-3 H 桥型电路基本结构

上图展示的是 H 桥电路的基本结构,由左右两组推挽电路构成。其中【O1】和【O2】端子用于连接电机。当左上和右下开关导通时,电流路径为:左上→【O1】→电机→【O2】→右下;当左下和右上开关导通时,电流方向变为:右上→【O2】→电机→【O1】→左下。通过切换导通状态可改变电流流向,从而实现电机的正反转控制。

在这里插入图片描述

图5-4 TB6612 电机驱动模块的硬件电路

在这里插入图片描述

图5-5 TB6612 电机驱动模块引脚定义

图5-4展示了TB6612电机驱动模块的硬件电路连接方式:

  • 【VM】接【电机电源】正极,需使用独立的大电流电源(与舵机供电方式相同)。如图5-5所示,【VM】为驱动电压输入端,支持4.5V-10V输入,建议与电机额定电压匹配(例如5V电机接5V电源)。
  • 【VCC】是逻辑电平输入端,范围是 2.7V 到 5.5V,这个要和控制器的电源保持一致,比如 STM32 是 3.3V 器件,【VCC】就接 3.3V。若是 51 单片机,是 5V 的器件,则【VCC】接 5V。
  • 所有【GND】端子内部互通,连接系统负极时只需接一个即可。
  • 【AO1】【AO2】接电机两极。
  • 【PWMA】【AIN1】【AIN2】直接接 STM32 GPIO 口即可,【PWMA】要接 PWM 输出端,这三个引脚提供低功率控制信号,驱动电路将从【VM】获取电流驱动电机,实现小信号控制大功率设备。
    B 路同理。
  • 【STBY】是待机控制引脚,若接 GND,则驱动芯片不工作,处于待机状态,若接逻辑电源 VCC,则芯片正常工作。无需待机功能时,建议直接连接VCC。需要待机控制时,可连接任意GPIO引脚,通过输出高低电平来切换工作/待机状态。

在这里插入图片描述

图5-6 电机方向和转速控制电平对照表

上图中:

  • 当【IN1】【IN2】为高电平,则【O1】【O2】输出低电平,【O1】【O2】没有电压差,电机不工作。
  • 当【IN1】【IN2】为低电平,则【O1】【O2】关闭,电机不工作。

由此可见,只有当【IN1】和【IN2】电平不同时(一高一低),电机才可能运转。此时电机的运行状态完全取决于【PWM】信号电平,这正是利用PWM技术实现模拟信号等效控制的关键所在。

以上就是直流电机的所有内容。

实验:PWM 驱动 LED 呼吸灯

B站空降链接

重映射TIM2_CH1(这部分暂时不做记录)

实验材料

  • STM32F103C8T6
  • 0.96 寸 4 脚OLED 显示屏
  • LED 灯
  • ST-LINK 仿真器
  • 面包板

在这里插入图片描述

图6-1 PWM 驱动 LED 呼吸灯接线图

需要注意的是,这里 LED 的正极接在 PA0 引脚,负极接在 GND,与之前的连接方式相反,所以现在高电平点亮,低电平熄灭。

实验目标
通过配置 TIM2 通用定时器产生 1kHz 频率、1% 分辨率的 PWM 信号输出至 PA0 引脚,驱动 LED 实现呼吸效果。TIM2 采用内部时钟源作为驱动时钟。
在 OLED 中显示 CCR 值。(这是我自己加上的)

实验原理
已知:
P W M 频率 = C K _ C N T A R R + 1 = C K _ P S C P S C + 1 A R R + 1 P W M 分辨率 = 1 A R R + 1 PWM频率 = \frac{CK\_CNT}{ARR + 1} = \frac{\frac{CK\_PSC}{PSC+1}}{ARR+1} \\ PWM分辨率 = \frac{1}{ARR+1} PWM频率=ARR+1CK_CNT=ARR+1PSC+1CK_PSCPWM分辨率=ARR+11
代入数值得:
A R R + 1 = 100 P S C + 1 = C K _ P S C ( A R R + 1 ) × P W M 频率 = 72 , 000 , 000 100 × 1000 = 720 \begin{aligned} &ARR + 1 = 100 \\ &PSC + 1 = \frac{CK\_PSC}{(ARR+1) \times PWM频率} = \frac{72,000,000}{100 \times 1000} = 720 \end{aligned} ARR+1=100PSC+1=(ARR+1)×PWM频率CK_PSC=100×100072,000,000=720

核心代码

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

#define ARR (100 - 1)
#define PSC (720 - 1)

uint8_t pwm_resolutoin;    // PWM 分辨率

int main(void)
{
	OLED_Init();
	OLED_ShowString(1, 1, "CCR:");
	
	/* 1.开启 TIM、GPIO 时钟 */
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    // 开启通用定时器TIM2时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    // 开启 GPIOA 时钟
	
	/* 2.配置时基单元 */
	
	TIM_InternalClockConfig(TIM2);    // 通用定时器TIM2的时钟源设置为内部时钟(定时器上电默认选择内部时钟,所以该行代码可以不写,但为了步骤完整还是协商比较好)
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    // 设置外部时钟的输入滤波器采样频率,不影响计数周期,本次实验可随便选
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    // 计数器选择向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period = ARR;    // 自动重装器值 ARR 设置为为 99,即计数 100 次
	TIM_TimeBaseInitStructure.TIM_Prescaler = PSC;    // 预分频器值 PSC 设置为 719,即分频系数为 720,将内部时钟从 72,000,000Hz 降到 100000Hz,即 100kHz,此为 CK_CNT
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;    // 重复计数器值设置为 0,因为TIM2是通用定时器,没有该模块
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);    // 初始化时基单元
	
	/* 3.配置输出比较单元 */
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);    // 给配置结构体赋值默认值,因为部分配置项不需要在通用定时器中使用,为避免出现错误,可先赋值默认值,再修改需要使用的配置项
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;    // 设置输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    // 设置输出比较匹配时的极性,TIM_OCPolarity_High 表示比较匹配时置高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    // 设置输出使能状态,决定能否输出 PWM 到引脚
	TIM_OCInitStructure.TIM_Pulse = 0;    // 设置捕获/比较寄存器 CCR 的初始值为 0,即占空比为 0%,LED 是熄灭状态
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);    // 设置 TIM2 使用 OC1 通道输出 PWM 信号
	
	/* 4.配置 GPIO */
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;    // TIM2 使用 OC1 通道输出信号需要引脚具备 TIM2_CH1 的功能,查阅手册可以得知 PA0 符合条件
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    // 选择复用推挽输出模式。开漏/推挽输出是由输出数据寄存器控制的,若想让定时器控制引脚,就需要使用复用开漏/推挽输出模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	/* 5.运行控制 */
	
	TIM_Cmd(TIM2, ENABLE);    // 启动 TIM2 通用定时器,这样 PWM 波形就可以通过 PA0 引脚输出了
	
	while(1)
	{
		for(pwm_resolutoin = 0; pwm_resolutoin < ARR; pwm_resolutoin++)
		{
			OLED_ShowNum(1, 5, pwm_resolutoin, 3);
			TIM_SetCompare1(TIM2, pwm_resolutoin);    // 设置 CCR
			Delay_ms(10);
		}
		for(pwm_resolutoin = ARR; pwm_resolutoin > 0; pwm_resolutoin--)
		{
			OLED_ShowNum(1, 5, pwm_resolutoin, 3);
			TIM_SetCompare1(TIM2, pwm_resolutoin);    // 设置 CCR
			Delay_ms(10);
		}
	}
}

实验:PWM 驱动舵机

B站空降链接

实验材料

  • STM32F103C8T6
  • 0.96 寸 4 脚OLED 显示屏
  • SG90 舵机
  • 直插 2 脚轻触开关
  • ST-LINK 仿真器
  • 面包板

在这里插入图片描述

图7-1 PWM 驱动舵机接线图

舵机正极接 5V 电压,STM32 无法提供足够电压,无法驱动舵机,所以接在 ST-LINK 仿真器的 5V 输出,由 USB 提供。

实验目标
在这里插入图片描述
利用 TIM2-CH2 输出 50Hz PWM(0.5 - 2.5ms 脉宽对应 0~180°),每按一次 PB1 按键角度增加 30°,超 180° 归零,并在 OLED 屏上显示当前角度值。

实验原理
F P W M = 1 s 20 m s = 1 0.02 = 50 H z F_{PWM} = \frac{1s}{20ms} = \frac{1}{0.02} = 50Hz FPWM=20ms1s=0.021=50Hz

F P W M = C K _ C N T A R R + 1 = C K _ P S C P S C + 1 A R R + 1 = 72 , 000 , 000 P S C + 1 A R R + 1 = 50 F_{PWM} = \frac{CK\_CNT}{ARR + 1} = \frac{\frac{CK\_PSC}{PSC+1}}{ARR+1} = \frac{\frac{72,000,000}{PSC+1}}{ARR+1} = 50 FPWM=ARR+1CK_CNT=ARR+1PSC+1CK_PSC=ARR+1PSC+172,000,000=50

PSC 和 ARR 的值并不是唯一的,可以自己多次尝试,这里直接使用:
P S C + 1 = 72 A R R + 1 = 20 , 000 PSC + 1 = 72 \\ ARR + 1 = 20,000 PSC+1=72ARR+1=20,000

故:
− 90 ° : C C R A R R + 1 = 0.5 m s 20 m s ,则 C C R = 0.5 × 20000 20 = 500 -90°:\frac{CCR}{ARR+1} = \frac{0.5ms}{20ms},则CCR = \frac{0.5 \times 20000}{20} = 500 90°ARR+1CCR=20ms0.5ms,则CCR=200.5×20000=500

90 ° : C C R A R R + 1 = 2.5 m s 20 m s ,则 C C R = 2.5 × 20000 20 = 2500 90°:\frac{CCR}{ARR+1} = \frac{2.5ms}{20ms},则CCR = \frac{2.5 \times 20000}{20} = 2500 90°ARR+1CCR=20ms2.5ms,则CCR=202.5×20000=2500

得到 CCR 的取值范围为 500 到 2500。

核心代码

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

#define ARR (20000 - 1)
#define PSC (72 - 1)

uint8_t Angle;    // 舵机旋转轴角度

int main(void)
{
	OLED_Init();
	OLED_ShowString(1, 1, "Angle:");
	OLED_ShowNum(1, 7, Angle, 3);
	
	/* 1.开启 TIM、GPIO 时钟 */
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    // 开启通用定时器 TIM2 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    // 开启 GPIOA 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);    // 开启 GPIOB 时钟
	
	/* 2.配置时基单元 */
	
	TIM_InternalClockConfig(TIM2);    // 通用定时器TIM2的时钟源设置为内部时钟(定时器上电默认选择内部时钟,所以该行代码可以不写,但为了步骤完整还是协商比较好)
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    // 设置外部时钟的输入滤波器采样频率,不影响计数周期,本次实验可随便选
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    // 计数器选择向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period = ARR;    // 自动重装器值 ARR 设置为为 19999,即计数 20000 次
	TIM_TimeBaseInitStructure.TIM_Prescaler = PSC;    // 预分频器值 PSC 设置为 71,即分频系数为 72,将内部时钟从 72,000,000Hz 降到 1,000,000Hz,即 1MHz,此为 CK_CNT
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;    // 重复计数器值设置为 0,因为TIM2是通用定时器,没有该模块
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);    // 初始化时基单元
	
	/* 3.配置输出比较单元 */
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);    // 给配置结构体赋值默认值,因为部分配置项不需要在通用定时器中使用,为避免出现错误,可先赋值默认值,再修改需要使用的配置项
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;    // 设置输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    // 设置输出比较匹配时的极性,TIM_OCPolarity_High 表示比较匹配时置高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    // 设置输出使能状态,决定能否输出 PWM 到引脚
	TIM_OCInitStructure.TIM_Pulse = 0;    // 设置捕获/比较寄存器 CCR 的初始值为 0,即占空比为 0%,LED 是熄灭状态
	TIM_OC2Init(TIM2,&TIM_OCInitStructure);    // 设置 TIM2 使用 OC2 通道输出 PWM 信号
	
	/* 4.配置 GPIO */
	
	GPIO_InitTypeDef GPIO_InitStructure_PWM;
	GPIO_InitStructure_PWM.GPIO_Pin = GPIO_Pin_1;    // TIM2 使用 OC2 通道输出信号需要引脚具备 TIM2_CH2 的功能,查阅手册可以得知 PA1 符合条件
	GPIO_InitStructure_PWM.GPIO_Mode = GPIO_Mode_AF_PP;    // 选择复用推挽输出模式。开漏/推挽输出是由输出数据寄存器控制的,若想让定时器控制引脚,就需要使用复用开漏/推挽输出模式
	GPIO_InitStructure_PWM.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure_PWM);
	
	GPIO_InitTypeDef GPIO_InitStructure_Button;
	GPIO_InitStructure_Button.GPIO_Mode = GPIO_Mode_IPU;    // 选择上拉输入模式,默认高电平
	GPIO_InitStructure_Button.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure_Button.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure_Button);
	
	/* 5.运行控制 */
	
	TIM_Cmd(TIM2, ENABLE);    // 启动 TIM2 通用定时器,这样 PWM 波形就可以通过 PA0 引脚输出了
	TIM_SetCompare2(TIM2, 500);    // 舵机旋转轴角度归零(复位)
	
	while(1)
	{
		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			// 消除按键抖动
			Delay_ms(20);
			while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
			Delay_ms(20);
			
			Angle += 30;    // 每次按键旋转30°
			
			// 超过舵机旋转角度则复位
			if(Angle > 180)
			{
				Angle = 0;
			}
			TIM_SetCompare2(TIM2, Angle / 180.0 * 2000 + 500);    // 将角度转换为 CCR
			OLED_ShowNum(1, 7, Angle, 3);
		}
	}
}

实验:PWM 驱动直流电机

B站空降链接

实验材料

  • STM32F103C8T6
  • 0.96 寸 4 脚OLED 显示屏
  • 直流电机
  • TB6612 电机驱动模块
  • ST-LINK 仿真器
  • 面包板

在这里插入图片描述

图8-1 PWM 驱动直流电机接线图

实验目标
在 STM32 上利用 PWM 驱动直流电机,通过按键循环调速:每次按下按键(消抖后)速度 +20,超过 100 后自动归零并立即反转方向;转速值始终保持在 0-100 之间;OLED 实时显示带符号的转速值(-100到100)。

核心代码

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

#define ARR (200 - 1)
#define PSC (720 - 1)

uint8_t Speed;    // 直流电机转速,速度范围规定为 0 ~ 100
int8_t Direaction = 1;    // 旋转方向,1 代表正转,-1 代表反转

int main(void)
{
	OLED_Init();
	OLED_ShowString(1, 1, "Speed:");
	OLED_ShowSignedNum(1, 7, Speed, 3);
	
	/* 1.开启 TIM、GPIO 时钟 */
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    // 开启通用定时器 TIM2 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    // 开启 GPIOA 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);    // 开启 GPIOB 时钟
	
	/* 2.配置时基单元 */
	
	TIM_InternalClockConfig(TIM2);    // 通用定时器TIM2的时钟源设置为内部时钟(定时器上电默认选择内部时钟,所以该行代码可以不写,但为了步骤完整还是协商比较好)
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    // 设置外部时钟的输入滤波器采样频率,不影响计数周期,本次实验可随便选
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    // 计数器选择向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period = ARR;    // 自动重装器值 ARR 设置为为 99,即计数 100 次
	TIM_TimeBaseInitStructure.TIM_Prescaler = PSC;    // 预分频器值 PSC 设置为 719,即分频系数为 720,将内部时钟从 72,000,000Hz 降到 100000Hz,即 100kHz,此为 CK_CNT
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;    // 重复计数器值设置为 0,因为TIM2是通用定时器,没有该模块
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);    // 初始化时基单元
	
	/* 3.配置输出比较单元 */
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);    // 给配置结构体赋值默认值,因为部分配置项不需要在通用定时器中使用,为避免出现错误,可先赋值默认值,再修改需要使用的配置项
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;    // 设置输出比较模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    // 设置输出比较匹配时的极性,TIM_OCPolarity_High 表示比较匹配时置高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    // 设置输出使能状态,决定能否输出 PWM 到引脚
	TIM_OCInitStructure.TIM_Pulse = 0;    // 设置捕获/比较寄存器 CCR 的初始值为 0,即占空比为 0%,LED 是熄灭状态
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);    // 设置 TIM2 使用 OC3 通道输出 PWM 信号
	
	/* 4.配置 GPIO */
	
	GPIO_InitTypeDef GPIO_InitStructure_PWM;
	GPIO_InitStructure_PWM.GPIO_Pin = GPIO_Pin_2;    // TIM2 使用 OC3 通道输出信号需要引脚具备 TIM2_CH3 的功能,查阅手册可以得知 PA2 符合条件
	GPIO_InitStructure_PWM.GPIO_Mode = GPIO_Mode_AF_PP;    // 选择复用推挽输出模式。开漏/推挽输出是由输出数据寄存器控制的,若想让定时器控制引脚,就需要使用复用开漏/推挽输出模式
	GPIO_InitStructure_PWM.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure_PWM);    // 配置 PWM 输出引脚
	
	GPIO_InitTypeDef GPIO_InitStructure_Motor;
	GPIO_InitStructure_Motor.GPIO_Mode = GPIO_Mode_Out_PP;    // 选择推挽输出模式
	GPIO_InitStructure_Motor.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure_Motor.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure_Motor);    // 配置直流电机方向控制引脚
	
	GPIO_InitTypeDef GPIO_InitStructure_Button;
	GPIO_InitStructure_Button.GPIO_Mode = GPIO_Mode_IPU;    // 选择上拉输入模式,默认高电平
	GPIO_InitStructure_Button.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure_Button.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure_Button);    // 配置按钮引脚
	
	/* 5.运行控制 */
	
	TIM_Cmd(TIM2, ENABLE);    // 启动 TIM2 通用定时器,这样 PWM 波形就可以通过 PA2 引脚输出了
	// TIM_SetCompare3(TIM2, 0);    // 舵机旋转轴角度归零(复位)
	
	while(1)
	{
		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			// 消除按键抖动
			Delay_ms(20);
			while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
			Delay_ms(20);
			
			Speed += 20;
			if(Speed > 100)    // 速度超过设置范围时归零且切换旋转方向
			{
				Speed = 0;
				Direaction = -Direaction;
			}
			
			if(Direaction == 1)
			{
				GPIO_SetBits(GPIOA, GPIO_Pin_4);
				GPIO_ResetBits(GPIOA, GPIO_Pin_5);
			}
			else
			{
				GPIO_ResetBits(GPIOA, GPIO_Pin_4);
				GPIO_SetBits(GPIOA, GPIO_Pin_5);
			}
			
			TIM_SetCompare3(TIM2, Speed);    // 设置 CCR 控制旋转速度
			OLED_ShowSignedNum(1, 7, Speed * Direaction, 3);
		}
	}
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白日梦想家之练气期

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值