STM32应用开发实践教程:智能小车电机调速模块的应用开发

3.3.1 任务分析
本任务要求设计一个可实现智能小车电机调速的应用程序,具体要点说明如下。
① 电机驱动部分选用德州仪器(Texas Instruments,TI)公司的 DRV8848 芯片(也可选
用其他芯片)。
② 支持同时对两个直流电机进行控制。
③ 支持按键控制,使用 4 个按键,它们的功能描述如下:
 Key1 控制电机正转,若电机当前处于停止状态,按下 Key1 则使之正转,若电机当前处
于正转或反转状态,按下 Key1 则使之停止;
 Key2 控制电机反转,若电机当前处于停止状态,按下 Key2 则使之反转,若电机当前处
于正转或反转状态,按下 Key2 则使之停止;
 Key3 控制电机减小转速,若电机当前为正转则使之正向减速,反之亦然;
 Key4 控制电机增大转速,若电机当前为正转则使之正向加速,反之亦然。
分析本任务的要求,若须控制 DRV8848 芯片以驱动电机转动,则应利用 STM32F4 系列微
控制器的定时器输出 PWM 信号。基本定时器不具备外部通道,故应采用高级控制定时器或通用
定时器。另外,本任务要求同时控制两个直流电机,一个直流电机的控制需要两路 PWM 信号,
因此程序应具备同时输出 4 路 PWM 信号的功能。
本任务涉及的知识点有:
 STM32F4 系列微控制器高级控制定时器的功能特性;
 STM32F4 系列微控制器的定时器输出 PWM 信号的编程配置方法;
 DRV8848 电机驱动芯片的工作原理与编程配置方法。
3.3.2 知识链接
1.STM32F4 系列微控制器的高级控制定时器和通用定时器概述
STM32F4 系列微控制器的高级控制定时器和通用定时器相对基本定时器来说,增加了外部
通道引脚,支持输入捕获、输出比较等功能,部分定时器还支持增量(正交)编码器和霍尔传感
器电路接口。高级控制定时器相比通用定时器,又增加了可编程死区互补输出、重复计数器和刹
车(断路)等工业电机控制的高级功能。
表 3-3-1 列出了高级控制定时器和通用定时器的部分外部通道的引脚(此处以 144 引脚的
STM32F407ZGT6 微控制器为例)。鉴于篇幅限制,表 3-3-1 仅介绍了具备 4 个以上外部通道的
定时器的引脚分布情况,其他内容可参考《STM32F4xx 中文参考手册》。

 

2.高级控制定时器功能框图解析
根据 3.3.1 节的任务分析可知,该任务应选取高级控制定时器来完成。由于高级控制定时器
相比基本定时器增加了外部通道和其他功能,因此其功能框图更加复杂。为了便于理解,本小节
将功能框图分为两部分,分别如图 3-3-1 和图 3-3-2 所示,在阅读时应注意它们之间的联系。
由这两张图可知,高级控制定时器由 7 部分构成,即:时钟源、控制器模块、时基单元、输
入捕获模块、捕获/比较寄存器组、输出比较模块和断路功能模块。下面分别对它们进行介绍。
(1)时钟源
从图 3-3-1 中可以看到,高级控制定时器的时钟源有 4 个选择。

 

 内部时钟(CK_INT);
 外部时钟模式 1,来自外部输入通道 TIx(x= 1,2,3,4);
 外部时钟模式 2,来自外部触发输入 ETR;
 内部触发输入,ITRx(x= 0,1,2,3)。
上述 4 个时钟源应用最多的是内部时钟,通过配置从模式控制寄存器(TIMx_SMCR)的
“SMS”位段为“000”即可使其生效。
(2)控制器模块
高级控制定时器的控制器模块包括触发控制器、从模式控制器和编码器接口。
触发控制器可为 MCU 芯片内部其他外设提供触发信号,如提供时钟信号给其他定时器、触
发 ADC/DAC 启动转换等。
从模式控制器用于控制计数器复位、启动、递增计数和递减计数等。
编码器接口是专门为增量(正交)编码器计数设计的,在“电机测速”等应用中引入编码器
接口可极大地减少开发者的编程工作量。
(3)时基单元
高级控制定时器的时基单元相比于基本定时器和通用定时器的时基单元,增加了重复计数器
寄存器(TIMx_RCR),该寄存器长度是 8bit,为高级控制定时器所特有。此外,3 种定时器的时
基单元均包含计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器
(TIMx_ARR),3 个寄存器的长度均为 16bit。
高级控制定时器的计数器功能相比其他两类定时器更强,其除了支持递增计数模式外,还支
持递减计数和中心对齐计数模式。
在中心对齐计数模式下,计数器先从 0 开始递增计数,直到计数值等于“TIMx_ARR-1”后
发生计数器上溢,然后从 TIMx_ARR 值开始递减计数直到发生计数器下溢。接着再从 0 开始计
数,如此循环。每次发生计数器上溢和下溢且重复计数器寄存器的值达到零时,都会生成更新事
件(UEV)。这意味着如果重复计数器寄存器的值设置为 N ,且发生 N +1 个计数器上溢或下溢,
则会生成更新事件,进而数据将从预装载寄存器转移到影子寄存器(其中包括自动重载寄存器、
预分频器寄存器以及比较模式下的捕获/比较寄存器)。
重复计数器在下列情况下将递减:
 递增计数模式下发生计数器上溢;
 递减计数模式下发生计数器下溢;
 中心对齐模式下发生计数器上溢和计数器下溢。
图 3-3-3 给出了一个不同计数模式和 TIMx_RCR 设置下的更新频率示例,该示例可帮助理
解时基单元的计数过程。
(4)输入捕获模块
输入捕获模块的作用是输入信号的上升沿、下降沿或者双边沿并进行捕获,其可被用于测量
输入信号的脉宽或者输入 PWM 信号的频率和占空比。
被测量的信号由外部通道 TIMx_CH1/ TIMx_CH2/ TIMx_CH3/ TIMx_CH4 进入,编号为 TI1/
TI2/ TI3/ TI4。信号经过输入滤波器滤除干扰信号,并经过边沿检测器确定信号极性后,被送入
捕获通道 IC1/IC2/IC3/IC4。预分频器用于设置捕获信号的时间,即多少个脉冲捕获一次。
通过预分频器的信号 ICxPS 是最终被捕获的信号,发生输入捕获时:
 发生有效跳变沿时,捕获/比较寄存器(TIMx_CCRx)会获取计数器的值;
 将 CC1IF 标志位置 1(中断标志),如果至少发生了两次连续捕获,但 CC1IF 标志位未
被清零,则 CC1OF 捕获溢出标志位会被置 1;
 根据 CC1IE 标志位生成中断;
 根据 CC1DE 标志位生成 DMA 请求。
(5)捕获/比较寄存器组
捕获/比较寄存器组是输入捕获模块和输出比较模块的共用部分。
(6)输出比较模块
输出比较模块的作用是通过定时器的外部引脚对外输出控制信号,其可以设置为 8 种不同的
工作模式,即冻结、将通道 x(x=1,2,3,4)设置为匹配时输出有效电平、将通道 x 设置为匹
配时输出无效电平、翻转、强制变为无效电平、强制变为有效电平、PWM1 和 PWM2。PWM 信
号输出是定时器输出比较模式中的特例,使用频率较高。

 当 TIMx_CNT 的值与 TIMx_CCR 的值相等时,输出参考波形 OCxREF 的信号极性就会发生
改变,并且会产生比较中断 CCxI,相应的标志位 CCxIF(状态寄存器中)也会被置位。我们将
OCxREF=1(高电平)称为有效电平,OCxREF=0(低电平)称为无效电平。
参考波形 OCxREF 可插入死区时间,用于生成两路互补的输出信号 OCx 和 OCxN。死区时
间的长短由断路和死区寄存器(BDTR)的位 DTG[7:0]配置。
进入输出控制电路的信号会被分成两路:一路是原始信号,另一路是被反向的信号。信号最
终是通过定时器的外部引脚(分别为 CH1/CH2/CH3/CH4)来输出的。对于高级控制定时器而言,
CH1/CH2/CH3 这 3 个输出通道各自还具备与信号极性互补的输出通道 CH1N/CH2N/CH3N。
(7)断路功能模块
断路功能是电机控制的刹车功能。使能断路功能时,根据相关控制位状态修改输出信号电平。
断路源可以是时钟故障事件,由内部复位时钟控制器中的时钟安全系统(CSS)生成,也可来自外
部断路输入 I/O(TIMx_BKIN)。
3.输出比较的典型应用——PWM 输出
(1)PWM 概述
脉冲宽度调制(Pulse Width Modulation,PWM)简称脉宽调制,是一种利用微处理器的数字
输出对模拟电路进行控制的非常有效的技术,被广泛应用于测量、通信、功率控制与变换等领域。
脉冲宽度调制可对模拟信号电平进行数字编码。通过使用高分辨率计数器来调制方波的占空
比,可对一个具体模拟信号的电平进行数字编码。PWM 信号是数字信号,因为在给定的任何时
刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)
或断(OFF)的重复脉冲序列被加载到模拟负载上去的。通的时候即是直流供电被加载到负载上,
断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用 PWM 进行数字编码。
图 3-3-4 是 STM32F4 微控制器定时器输出 PWM 信号(PWM 信号生成过程)的示意。

 PWM 信号的生成与计数器寄存器(TIMx_CNT)、自动重载寄存器(TIMx_ARR)、捕获/比较
寄存器(TIMx_CCRy)以及捕获/比较使能寄存器(TIMx_CCER)关系紧密。图 3-3-4 中的 x
为定时器编号,取值范围 1~14,y 为捕获/比较通道编号,取值范围 1~4。如 TIM1_CCR2 表示
定时器 1 的捕获/比较寄存器 2。
图3-3-4 设置TIMx_ARR 的值为100,TIMx_CCRy 的值为30。设置定时器为递增计数模式,
TIMx_CNT 的值从 0 开始计数。当 TIMx_CNT<TIMx_CCRy 时,PWM 输出有效电平(高电平);当
TIMx_CCRy≤TIMx_CNT<TIMx_ARR时,PWM 输出无效电平(低电平);当TIMx_CNT=TIMx_ARR
时,TIMx_CNT 又从 0 开始计数,如此循环往复。
由此可见,PWM 信号的频率由TIMx_ARR的值来决定,其占空比则由TIMx_CCRy 的值来决定。
有效和无效电平的高低由TIMx_CCER 的CCxP 位决定。当CCxP 位配置为0 时,高电平为有效
电平;当CCxP 位配置为1时,低电平为有效电平。
(2)PWM 输出模式
高级控制定时器的 PWM 输出模式有两种:PWM1 和 PWM2。这两种模式在 CCxp 位配置为
0 的情况下的区别见表 3-3-2。        

由表 3-3-2 可知,图 3-3-4 中所示的 PWM 生成过程工作在 PWM1 模式下,计数器模式为
递增,CCxP位配置为0。
计数模式为递增、CCxP 位配置为 0 时的 PWM2 模式下的 PWM 信号生成过程示意如图
3-3-5 所示。 

在两种 PWM 输出模式下,配置定时器以控制寄存器 1(TIMx_CR1)的“CMS”位段时,
可选模式如下。
 00:边沿对齐模式。计数器根据方向位(DIR)递增计数或递减计数。
 01:中心对齐模式 1。计数器交替进行递增计数和递减计数。仅当计数器递减计数时,
配置为输出的通道捕获/比较模式寄存器中的 CxS=00)的输出比较中断标志位才置 1。
 10:中心对齐模式 2。计数器交替进行递增计数和递减计数。仅当计数器递增计数时,
配置为输出的通道捕获/比较模式寄存器中的 CxS=00)的输出比较中断标志位才置 1。
 11:中心对齐模式3。计数器交替进行递增计数和递减计数。当计数器递增计数或递减计数
时,配置为输出的通道捕获/比较模式寄存器中的CxS=00)的输出比较中断标志位都会置1。
图 3-3-4 和图 3-3-5 展示了边沿对齐模式下的 PWM 信号,下面介绍中心对齐模式下的
PWM 信号。中心对齐模式下的 PWM 波形示意如图 3-3-6 所示。

 

图 3-3-6 是一张 PWM1 模式下的中心对齐 PWM 波形示意图,TIMx_ARR 值设置为 8,
TIMx_CCRx 值分别设置为 4 和 7,计数器 CNT 工作在递增/递减计数模式。该波形图生成过程描
述如下。
 将中心对齐 PWM 信号生成分为两个阶段,图中标号①所处位置为第一阶段,标号②所
处位置为第二阶段。
 第一阶段计数器 CNT 工作在递增计数模式,从 0 开始计数,当 TIMx_CNT<TIMx_CCRx
时,OCxREF 输出有效电平(高电平);当 TIMx_CCRx≤TIMx_CNT<TIMx_ARR 时,
OCxREF 输出无效电平(低电平)。
 第二阶段计数器 CNT 工作在递减计数模式,TIMx_ARR 值从 8 开始递减,当 TIMx_CNT>
TIMx_CCRx 时,OCxREF输出无效电平(低电平);TIMx_CNT≤TIMx_CCRx 时,OCxREF
输出有效电平(高电平)。
 3 种中心对齐模式的区别在于输出比较中断标志位 CCxIF 置 1 的时机。中心对齐模式 1
在计数器 CNT 递减计数时置 1;中心对齐模式 2 在计数器 CNT 递增计数时置 1;中心
对齐模式 3 则在计数器 CNT 递增和递减计数时都置 1。
4.高级控制定时器初始化结构体介绍
STM32F4 标准外设库为定时器外设提供了 4 个初始化结构体,任务 3.1 中已学习了定时器
基本初始化结构体(TIM_TimeBaseInitTypeDef),本小节将对高级控制定时器适用的输出比较
初始化结构体(TIM_OCInitTypeDef)、输入捕获初始化结构体(TIM_ICInitTypeDef)和断路与
死区初始化结构体(TIM_BDTRInitTypeDef)进行介绍。
(1)输出比较初始化结构体
该结构体用于输出比较模式,配置定时器的输出通道的工作参数,如比较输出模式、脉冲宽
度、输出极性、空闲状态下比较输出状态等,被 TIM_OCxInit()函数调用。其原型定义如下: 

typedef struct {
uint16_t TIM_OCMode;  // 比较输出模式
uint16_t TIM_OutputState;  // 比较输出使能
uint16_t TIM_OutputNState; // 比较互补输出使能
uint32_t TIM_Pulse; // 脉冲宽度
uint16_t TIM_OCPolarity;  // 输出极性
uint16_t TIM_OCNPolarity;  // 互补输出极性
uint16_t TIM_OCIdleState;  // 空闲状态下比较输出状态
uint16_t TIM_OCNIdleState; // 空闲状态下比较互补输出状态
} TIM_OCInitTypeDef;

输出比较初始化结构体的各成员变量的介绍如下。
① TIM_OCMode
比较输出模式配置,共有 8 种模式可供选择。“TIM_OCMode”实际配置捕获/比较模式寄存
器(TIMx_CCMRy)“OCxM[2:0]”位段的参数,可供配置的参数如下:
 冻结模式,TIMx_CNT 与 TIMx_CCR1 不会对输出造成影响(TIM_OCMode_Timing);
 TIMx_CNT 与 TIMx_CCR1 匹配时,输出有效电平(TIM_OCMode_Active);
 TIMx_CNT 与 TIMx_CCR1 匹配时,输出无效电平(TIM_OCMode_Inactive);
 TIMx_CNT 与 TIMx_CCR1 匹配时,输出翻转(TIM_OCMode_Toggle);
 PWM1 输出模式(TIM_OCMode_PWM1);
112
开发全案例实践
STM32 嵌入式技术应用
 PWM2 输出模式(TIM_OCMode_PWM2);
 强制输出有效电平(TIM_ForcedAction_Active);
 强制输出无效电平(TIM_ForcedAction_InActive)。
② TIM_OutputState
比较输出是否使能配置,决定最终的输出比较信号 OCx 是否通过外部引脚输出。
“TIM_OutputState”实际配置捕获/比较使能寄存器(TIMx_CCER)的“CCxE”位段的参数,
可供配置的参数如下:
 比较输出不使能(TIM_OutputState_Disable);
 比较输出使能(TIM_OutputState_Enable)。
③ TIM_OutputNState
比较互补输出是否使能配置,决定最终的输出比较信号 OCx 的互补信号 OCxN 是否通过外
部引脚输出。“TIM_OutputNState”实际配置捕获/比较使能寄存器(TIMx_CCER)的“CCxNE”
位段的参数,可供配置的参数如下:
 比较互补输出不使能(TIM_OutputNState_Disable);
 比较互补输出使能(TIM_OutputNState_Enable)。
④ TIM_Pulse
比较输出的脉冲宽度配置。“TIM_Pulse”实际配置捕获/比较寄存器(TIMx_CCRx)的参数,
可供配置的范围是 0~65535。
⑤ TIM_OCPolarity
比较输出的极性配置,可选输出比较信号 OCx 是高电平有效还是低电平有效,决定了定时
器输出通道的有效电平。“TIM_OCPolarity”实际配置捕获/比较使能寄存器(TIMx_CCER)的
“CCxP”位段的参数,可供配置的参数如下:
 OCx 输出高电平有效(TIM_OCPolarity_High);
 OCx 输出低电平有效(TIM_OCPolarity_Low)。
⑥ TIM_OCNPolarity
比较互补输出的极性配置,可选输出比较信号 OCx 的互补信号 OCxN 是高电平有效还是低
电平有效,决定了定时器互补输出通道的有效电平。“TIM_OCNPolarity”实际配置捕获/比较使
能寄存器(TIMx_CCER)的“CCxNP”位段的参数,可供配置的参数如下:
 OCx 互补输出高电平有效(TIM_OCNPolarity_High);
 OCx 互补输出低电平有效(TIM_OCNPolarity_Low)。
⑦ TIM_OCIdleState
空闲状态时通道输出电平配置。“TIM_OCIdleState”配置在空闲状态(BDTR_MOE 位为 0)
时经过死区时间后定时器通道输出高电平还是低电平,它实际配置控制寄存器 2(TIMx_CR2)
的“OISx”位段的参数,可供配置的参数如下:
 空闲状态通道输出低电平(TIM_OCIdleState_Reset);
 空闲状态通道输出高电平(TIM_OCIdleState_Set)。
⑧ TIM_OCNIdleState
空闲状态时互补通道输出电平配置。“TIM_OCNIdleState”配置在空闲状态(BDTR_MOE
位为 0)时经过死区时间后定时器互补通道输出高电平还是低电平,它实际配置控制寄存器 2
(TIMx_CR2)的“OISxN”位段的参数,可供配置的参数如下:
 空闲状态互补通道输出低电平(TIM_OCNIdleState_Reset);
 空闲状态互补通道输出高电平(TIM_OCNIdleState_Set)。
(2)输入捕获初始化结构体
该结构体用于输入捕获模式,配置定时器的输入通道的工作参数,如输入通道选择、输入
捕获触发边沿选择、输入捕获预分频器配置等,被 TIM_ICInit()函数调用。该结构体的原型定
义如下:

typedef struct {
uint16_t TIM_Channel;  // 输入通道选择
uint16_t TIM_ICPolarity;  // 输入捕获触发边沿选择
uint16_t TIM_ICSelection;  // 输入捕获选择
uint16_t TIM_ICPrescaler;  // 输入捕获预分频器
uint16_t TIM_ICFilter; // 输入捕获滤波器
} TIM_ICInitTypeDef;

输入捕获初始化结构体各成员变量介绍如下。
① TIM_Channel
输入通道(TIx)配置,可供配置的参数如下:
 通道 1(TIM_Channel_1);
 通道 2(TIM_Channel_2);
 通道 3(TIM_Channel_3);
 通道 4(TIM_Channel_4)。
② TIM_ICPolarity
输入捕获触发边沿选择。“TIM_ICPolarity”实际配置捕获/比较使能寄存器(TIMx_CCER)
的“CCxP”位段的参数,可供配置的参数如下:
 输入捕获上升沿触发(TIM_ICPolarity_Rising);
 输入捕获下降沿触发(TIM_ICPolarity_Falling);
 输入捕获双边沿触发(TIM_ICPolarity_BothEdge)。
③ TIM_ICSelection
输入捕获通道(ICx)的信号来源选择,其信号来源有 3 个选择:直接输入、间接输入和触
发输入。“TIM_ICSelection”实际配置捕获/比较模式寄存器(TIMx_CCMRx)“CCxS[1:0]”位
段的参数,可供配置的参数如下:
 直接输入(TIM_ICSelection_DirectTI);
 间接输入(TIM_ICSelection_IndirectTI);
 触发输入(TIM_ICSelection_TRC)。
④ TIM_ICPrescaler
输入捕获通道预分频比配置,它决定了捕获的频率,如果需要每检测到一个边沿就执行一次
捕获,则不需要分频。“TIM_ICPrescaler”实际配置捕获/比较模式寄存器(TIMx_CCMRx)
“ICxPSC[1:0]”位段的参数,可供配置的参数如下:
 无预分频器(TIM_ICPSC_DIV1);
 每发生 2 个事件执行一次捕获(TIM_ICPSC_DIV2);
114
开发全案例实践
STM32 嵌入式技术应用
 每发生 4 个事件执行一次捕获(TIM_ICPSC_DIV4);
 每发生 8 个事件执行一次捕获(TIM_ICPSC_DIV8)。
⑤ TIM_ICFilter
输入捕获滤波器配置。“TIM_ICFilter”用于定义 TIx 输入的采样频率和适用于 TIx 数字滤波
器的带宽,它实际配置捕获/比较模式寄存器(TIMx_CCMRx)“ICxF[3:0]”位段的参数。
(3)断路与死区初始化结构体
该结构体用于断路与死区参数的配置,如运行模式下的关闭状态选择、死区时间、断路输入
使能控制、自动输出使能等,被 TIM_BDTRConfig()函数调用。该结构体各成员变量主要对应断
路和死区寄存器(BDTR)。该结构体的原型定义如下:

typedef struct {
uint16_t TIM_OSSRState; // 运行模式下的关闭状态选择
uint16_t TIM_OSSIState; // 空闲模式下的关闭状态选择
uint16_t TIM_LOCKLevel; // 锁定配置
uint16_t TIM_DeadTime; // 死区时间
uint16_t TIM_Break; // 断路输入使能控制
uint16_t TIM_BreakPolarity; // 断路输入极性
uint16_t TIM_AutomaticOutput;  // 自动输出使能
} TIM_BDTRInitTypeDef;

本任务无须使用该结构体,故不对其进行详细介绍。

5.DRV8848 电机驱动芯片介绍
(1)DRV8848 概述
DRV8848 是 TI 公司出品的一款双路 H 桥电机驱动器,该器件可用于驱动一个或两个直流电
机、一个双极性步进电机或其他负载,一般与主控制器的 PWM 输出相连。
每个 H 桥驱动器都含有一个调节电路,可通过固定关断时间斩波方案调节绕组电流。H 桥
驱动器提供了低功耗睡眠模式,可将部分内部电路关断,从而实现极低的静态电流和功耗。其还
具有欠压闭锁(UVLO)、过流保护(OCP)、短路保护和过热保护等内部保护功能,此外,故障
状态可通过外部引脚指示。
DRV8848 的电气特性总结如下。
① 双路 H 桥电机驱动:
 单通道/双通道刷式直流电机;
 步进电机。
② 脉宽调制(PWM)控制接口。
③ 可选电流调节,具有 20 μs 固定关断时间。
④ 每个 H 桥均可提供高输出电流:
 单路最大驱动电流为 2 A(12 V 且 T A =25℃);
 并联模式下最大驱动电流为 4 A(12 V 且 T A =25℃)。
⑤ 工作电源电压范围为 4~18 V。
⑥ 3μA 低电流睡眠模式。
⑦ 保护特性:
 引脚 VM 欠压闭锁(UVLO)、过流保护(OCP)、热关断(TSD);
 故障状态指示引脚(nFAULT)。
(2)DRV8848 引脚功能
DRV8848 的芯片封装是 HTSSOP,共 16 个引脚,其引脚封装如图 3-3-7 所示。

DRV8848 芯片引脚功能描述如表 3-3-3 所示。 

(3)DRV8848 的典型应用电路及其工作过程解析
DRV8848 的简化电路连接框图如图 3-3-8 所示。微控制器生成 PWM 信号控制 DRV8848,
nFAULT 引脚输出异常状况指示,AOUT 和 BOUT 可分别接入一个直流电机,电压供应范围为 4~
18V。 

 

DRV8848 的典型应用电路的工作过程说明如下。
① AIN1 与 BIN1 并联,输入一路 PWM 信号;AIN2 与 BIN2 并联,输入另一路 PWM 信号。
② AOUT1 与 AOUT2 连接一个电机,BOUT1 与 BOUT2 连接另一个电机,由于 PWM 信号
输入部分并联,因此两个电机是联动的。
③ 电机调速方法说明如下:
 MCU 输出第一路 PWM 信号占空比为 0,第二 路 PWM 信号占空比越大则电机转速越快,
反之亦然;
 MCU 输出两路 PWM 信号占空比相同,则电机停止转动。
④ MCU 的 1 个 GPIO 端口连接 nSLEEP 引脚,输出高电平唤醒 DRV8848,输出低电平使
其进入低功耗睡眠模式。
⑤ MCU 的 1 个 GPIO 端口连接 nFAULT 引脚,用于接收 DRV8848 的错误状态指引信息。
⑥ VM 端连接 12 V 直流电压。
6.高级控制定时器输出 PWM 信号的编程配置步骤
掌握了高级控制定时器的功能特性、初始化结构体的配置、PWM 输出的工作原理以及电机
驱动芯片的编程配置方法等知识点以后,我们可以开始学习如何对高级控制定时器进行编程配
置,并使之输出 PWM 信号。以高级控制定时器 TIM1 为例,具体的编程配置步骤如下。
(1)开启 TIM1 时钟和输出比较通道 GPIO 的时钟,配置 GPIO 端口为复用功能
根据表3-1-1可知TIM1挂载在APB2上,因此需要调用ABP2的时钟使能函数来开启TIM1
的时钟。具体代码如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);  // 使能 TIM1 时钟
由于定时器输出 PWM 信号需要使用输出比较通道,因此需要配置相关的 GPIO 端口的工作
模式为复用模式,并映射为 TIM1 功能引脚,具体代码如下: 

GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
/* CH1:PE9 | CH2:PE11 | CH3:PE13 | CH4:PE14 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_11|GPIO_Pin_13|GPIO_Pin_14;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOE,&GPIO_InitStructure);
/* PE9|PE11|PE13|PE14  映射为 TIM1 外部通道 GPIO */
GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_TIM1);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_TIM1);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_TIM1);

(2)配置定时器的基本工作参数
该步骤主要通过配置“定时器基本初始化结构体(TIM_TimeBaseInitTypeDef)”的各成员变量,
然后调用TIM_TimeBaseInit()函数进行参数的初始化来完成。配置方法与基本定时器的类似,若未使
用输入捕获功能和重复计数功能,则无须关心“TIM_ClockDivision”和“TIM_RepetitionCounter”
的配置。具体代码如下:

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
/*  定时器基本工作参数初始化 */
TIM_TimeBaseInitStructure.TIM_Period = arr;  // 自动重载寄存器值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 预分频系数
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;// 递增计数
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);

(3)配置定时器输出比较功能的工作参数
定时器的基本工作参数配置好之后,还需要配置定时器输出比较功能的工作参数,如具体工
作在何种输出比较功能、输出通道(OCx)与互补输出通道(OCxN)是否使能、比较输出的极
性(高电平有效或低电平有效)、空闲状态下输出通道的电平状态等。
输出比较功能主要通过配置“定时器输出比较结构体(TIM_OCInitTypeDef)”的各成员变
量,然后调用 TIM_OCxInit()函数进行参数的初始化来完成。需要注意的是:4 路输出通道需要
分别配置。
配置 TIM1 工作在“PWM1”模式,输出通道(OCx)使能,互补输出通道(OCxN)失能,
比较输出信号极性为高电平有效,并使能预装载寄存器的具体代码如下:

TIM_OCInitTypeDef TIM_OCInitStructure;
/*  定时器输出比较功能初始化 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 输出比较模式为 PWM1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_Pulse = ccr1; // 通道 1 的 TIMx_CCR1 初值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(TIM1,&TIM_OCInitStructure); // 通道 1 初始化
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能预装载寄存器
TIM_OCInitStructure.TIM_Pulse = ccr2; // 通道 2 的 TIMx_CCR2 初值
TIM_OC2Init(TIM1,&TIM_OCInitStructure); // 通道 2 初始化
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能预装载寄存器
TIM_OCInitStructure.TIM_Pulse = ccr3; // 通道 3 的 TIMx_CCR3 初值
TIM_OC3Init(TIM1,&TIM_OCInitStructure); // 通道 3 初始化
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能预装载寄存器
TIM_OCInitStructure.TIM_Pulse = ccr4; // 通道 4 的 TIMx_CCR4 初值
TIM_OC4Init(TIM1,&TIM_OCInitStructure); // 通道 4 初始化
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能预装载寄存器

(4)使能定时器 TIM1,并配置主输出使能
完成以上配置以后,定时器暂时还无法输出 PWM 信号,需要使能定时器后才能满足要求。
同时,高级控制定时器还需要配置断路和死区寄存器(TIMx_BDTR)的“MOE”位段的值为 1,
才可使能 OCx 与 OCxN 通道输出。具体的代码如下:
TIM_Cmd(TIM1,ENABLE); // 使能定时器 1
/*  高级控制定时器需要配置 TIMx_BDTR 第 15 位 _MOE ,才可使能主输出 */
TIM_CtrlPWMOutputs(TIM1,ENABLE);
(5)修改 TIMx_CCRx 的值以控制 PWM 信号的占空比
遵循以上步骤完成高级控制定时器的配置后,即可在其 4 个输出通道检测到 PWM 信号。在
直流电机控制的实际应用中,电机的转速是频繁变化的,这就需要调节 PWM 信号的占空比。通
过前面的学习可知,PWM 信号的占空比是由捕获/比较寄存器(TIMx_CCRx)的值决定的,因此
我们还应知道调用 STM32F4 标准外设库的哪个函数可以控制 PWM 信号的占空比。
STM32F4 标准外设库提供了一个修改定时器捕获/比较寄存器(TIMx_CCRx)值的函数,该
函数的原型定义如下:
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1) ;
这个函数及其参数的说明如下。
① TIM_SetCompare1()函数对应设置“通道 1 寄存器 CCR1”的值,TIM_SetCompare2()
函数对应设置“通道 2 寄存器 CCR2”的值。以此类推可知此类函数共有 4 个。
② TIMx:定时器编号,如 TIM1。只有基本定时器 TIM6 和 TIM7 无法使用该函数。
③ Compare1:要设置的 TIMx_CCRx 的参数。前半部 TIMx 中的 x 由第一个参数决定,表
示定时器编号;后半部 CCRx 中的 x 的取值范围为 1~4,表示通道编号,具体如何表示取决于函
数调用,参见前述函数名的说明。
例如,如果要修改定时器 TIM1 的通道 4 的捕获/比较寄存器(TIM1_CCR4)的值为“90”,
可执行以下代码:
TIM_SetCompare4(TIM1, 90) ;
3.3.3 任务实施
1.硬件接线
根据表 3-3-4 所示的电机调速模块硬件接线表,将直流电机、DRV8848 电机驱动与
STM32F4 系列微控制器相连。

2.编写 TIM1 输出比较通道 GPIO 端口的功能配置程序
复制一份任务 3.2 的工程,并将其重命名为“task3.3_Timer_PWM_MotorControl”。在
“TIMER”目录下新建“timer1.c”和“timer1.h”两个文件,将它们加入工程中,并配置头文件
包含路径。在“timer1.c”文件中编写 TIM1 输出比较通道 GPIO 端口的功能配置程序。 

#include "timer1.h"
/**
* @brief Timer1 输出比较通道 GPIO 端口的功能配置
* @param None
* @retval None
*/
void TIM1_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
/* CH1:PE9 | CH2:PE11 | CH3:PE13 | CH4:PE14 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;// 复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_11| \
GPIO_Pin_13|GPIO_Pin_14;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOE,&GPIO_InitStructure);
/* PE9|PE11|PE13|PE14  复用为 TIM1 外部通道 GPIO */
GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_TIM1);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_TIM1);
GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_TIM1);
}

3.编写 TIM1 的时基和输出比较功能初始化程序
继续在“timer1.c”文件中输入以下代码:

/**
* @brief Timer1  时基和 PWM 输出功能初始化
* @param arr: 自动重载寄存器值, PSC: 预分频器分频系数
* @retval None
*/
void TIM1_PWM_Init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* SystemCoreClock = 168000000 */
uint32_t TimerPeriod = 1/(SystemCoreClock/(psc+1))*(arr+1);
uint32_t ccr1 = (arr+1) / 2; // 占空比 1/2 = 50%
uint32_t ccr2 = (arr+1) / 3; // 占空比 1/3 = 33%
uint32_t ccr3 = (arr+1) / 4; // 占空比 1/4 = 25%
uint32_t ccr4 = (arr+1) / 5; // 占空比 1/5 = 20%
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
/*  定时器基本初始化 */
TIM_TimeBaseInitStructure.TIM_Period = arr; // 自动重载寄存器值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 预分频系数
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;// 死区控制用
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;// 递增计数
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;// 重复计数器设置为 0
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);
/*  定时器输出比较功能初始化 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;// 输出比较模式为 PWM1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_Pulse = ccr1; // 通道 1 的 TIMx_CCR1 初值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(TIM1,&TIM_OCInitStructure); // 通道 1 初始化
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);// 使能预装载寄存器
TIM_OCInitStructure.TIM_Pulse = ccr2; // 通道 2 的 TIMx_CCR2 初值
TIM_OC2Init(TIM1,&TIM_OCInitStructure); // 通道 2 初始化
TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);// 使能预装载寄存器
TIM_OCInitStructure.TIM_Pulse = ccr3; // 通道 3 的 TIMx_CCR3 初值
TIM_OC3Init(TIM1,&TIM_OCInitStructure); // 通道 3 初始化
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable);// 使能预装载寄存器
TIM_OCInitStructure.TIM_Pulse = ccr4; // 通道 4 的 TIMx_CCR4 初值
TIM_OC4Init(TIM1,&TIM_OCInitStructure); // 通道 4 初始化
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能预装载寄存器
TIM_ARRPreloadConfig(TIM1,ENABLE); //ARPE 使能
TIM_Cmd(TIM1,ENABLE);  // 使能定时器 1
/*  高级控制定时器需要配置 TIMx_BDTR 的第 15 位 _MOE ,才可使能主输出 */
TIM_CtrlPWMOutputs(TIM1,ENABLE);
}

在“timer1.h”文件中输入以下代码:

#ifndef __TIMER1_H
#define __TIMER1_H
#include "sys.h"
void TIM1_GPIO_Config(void);
void TIM1_PWM_Init(uint16_t arr, uint16_t psc);
#endif

 4.编写直流电机调速程序
在“HARDWARE”文件夹下新建名为“MOTOR”的子文件夹。新建“motor.c”和“motor.h”
两个文件,将它们加入工程中,并配置头文件包含路径。在“motor.c”文件中编写控制直流电
机按指定速度正转、反转与停止的程序。

#include "motor.h"
/**
* @brief 小车前进,电机正转控制
* @param speed: 前进速度 (TIMx_CCR 值 )
* @retval None
*/
void Car_Forward(int16_t speed)
{
int16_t backSpeed;
nSLEEP = 1;
if(speed > 0) {
if(speed > 100) speed = 100;
/*  第一路电机控制 */
TIM_SetCompare1(TIM1, speed);
TIM_SetCompare2(TIM1, 0);
/*  第二路电机控制 */
TIM_SetCompare3(TIM1, speed);
TIM_SetCompare4(TIM1, 0);
}
else if(speed < 0) {
if(speed < -100) speed = -100;
backSpeed = -speed;
/*  第一路电机控制 */
TIM_SetCompare1(TIM1, backSpeed);
TIM_SetCompare2(TIM1, 0);
/*  第二路电机控制 */
TIM_SetCompare3(TIM1, backSpeed);
TIM_SetCompare4(TIM1, 0);
}
TIM_CtrlPWMOutputs(TIM1,ENABLE);
}
/**
* @brief 小车后退,电机反转控制
* @param speed: 后退速度 (TIMx_CCR 值 )
* @retval None
*/
void Car_Backup(int16_t speed)
{
int16_t backSpeed;
nSLEEP = 1;
if(speed > 0) {
if(speed > 100) speed = 100;
/*  第一路电机控制 */
TIM_SetCompare1(TIM1, 0);
TIM_SetCompare2(TIM1, speed);
/*  第二路电机控制 */
TIM_SetCompare3(TIM1, 0);
TIM_SetCompare4(TIM1, speed);
}
else if(speed < 0) {
if(speed < -100) speed = -100;
backSpeed = -speed;
/*  第一路电机控制 */
TIM_SetCompare1(TIM1, 0);
TIM_SetCompare2(TIM1, backSpeed);
/*  第二路电机控制 */
TIM_SetCompare3(TIM1, 0);
TIM_SetCompare4(TIM1, backSpeed);
}
TIM_CtrlPWMOutputs(TIM1,ENABLE);
}
/**
* @brief 小车停止控制
* @param None
* @retval None
*/
void Car_Stop(void)
{
nSLEEP = 1;
/*  第一路电机控制 */
TIM_SetCompare1(TIM1, 95);
TIM_SetCompare2(TIM1, 95);
/*  第二路电机控制 */
TIM_SetCompare3(TIM1, 95);
TIM_SetCompare4(TIM1, 95);
TIM_CtrlPWMOutputs(TIM1,ENABLE);
}

在“motor.h”文件中输入以下代码:

#ifndef __MOTOR_H
#define __MOTOR_H
#include "sys.h"
#define nSLEEP PBout(11)  //DRV8848 唤醒引脚
/*  直流电机工作状态枚举类型定义 */
typedef enum{
MOTOR_STOP = 0x00,
MOTOR_FORWARD,
MOTOR_BACKUP, 
} MOTOR_StateTypeDef;
void Car_Forward(int16_t speed);
void Car_Backup(int16_t speed);
void Car_Stop(void);
#endif

 5.编写 main()函数
在“main.c”文件中输入以下代码:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "timer1.h"
#include "motor.h"
static void Key_Process(void);
uint8_t keyValue = 0;
int16_t initSpeed = 70, newSpeed = 70;
MOTOR_StateTypeDef Motor_State = MOTOR_STOP;
int main(void)
{
delay_init(168); // 延时函数初始化
LED_Init(); //LED 端口初始化
Key_Init(); // 按键初始化
EXTIx_Init();  // 外部中断初始化
USART1_Init(115200);  //USART1 初始化
TIM1_GPIO_Config(); // 定时器 1 输出通道 GPIO 初始化
/*  定时器 1 PWM 输出功能初始化 */
TIM1_PWM_Init(100-1, 12-1);
printf("System Started!\r\n");
while(1)
{
Key_Process();
LED1 = ~LED1;
delay_ms(50);
}
return 0;
}
/**
* @brief 按键处理流程
* @param None
* @retval None
*/
static void Key_Process(void)
{
/* Key1_ 上键按下,电机正转,小车前进 */
if(keyValue == KEY_U_PRESS)
{
if(Motor_State == MOTOR_STOP)
{
Car_Forward(70); // 电机正转,小车前进
Motor_State = MOTOR_FORWARD;
}
else if(Motor_State == MOTOR_FORWARD || \
Motor_State == MOTOR_BACKUP)
{
Car_Stop();
Motor_State = MOTOR_STOP;
}
keyValue = 0;
}
/* Key2_ 下键按下,电机反转,小车后退 */
if(keyValue == KEY_D_PRESS)
{
if(Motor_State == MOTOR_STOP)
{
Car_Backup(70); // 电机反转,小车后退
Motor_State = MOTOR_BACKUP;
}
else if(Motor_State == MOTOR_FORWARD || \
Motor_State == MOTOR_BACKUP)
{
Car_Stop();
Motor_State = MOTOR_STOP;
}
keyValue = 0;
}
/* Key3_ 左键按下,减小占空比 */
if(keyValue == KEY_L_PRESS)
{
if(Motor_State == MOTOR_FORWARD)
{
newSpeed -= 2;
if(newSpeed < 0) newSpeed = 0;
Car_Forward(newSpeed);
}
else if(Motor_State == MOTOR_BACKUP)
{
newSpeed -= 2;
if(newSpeed < 0) newSpeed = 0;
Car_Backup(newSpeed);
}
keyValue = 0;
}
/* Key4_ 右键按下,增大占空比 */
if(keyValue == KEY_R_PRESS)
{
if(Motor_State == MOTOR_FORWARD)
{
newSpeed += 2;
if(newSpeed >= 100) newSpeed = 100;
Car_Forward(newSpeed);
}
else if(Motor_State == MOTOR_BACKUP)
{
newSpeed += 2;
if(newSpeed >= 100) newSpeed = 100;
Car_Backup(newSpeed);
}
keyValue = 0;
}
}

上述代码采用了运动控制中常用的“状态机(StateMachine)”的编程思想,具体说明如下:
① 在“motor.h”文件中定义了“电机工作状态枚举类型——MOTOR_StateTypeDef”;
② 在“main.c”文件的第 14 行中声明了一个枚举类型变量“Motor_State”,其初值为
“MOTOR_STOP”,代表电机初始工作状态为“停止”;
③ 在按键处理程序中,判断电机当前的工作状态,决定下一步的程序走向;
④ 当控制电机进入不同的工作状态之后,修改“Motor_State”枚举类型变量的值,如从
“MOTOR_STOP”变为“MOTOR_FORWARD”或者“MOTOR_BACKUP”;
⑤ 在 while(1)的下一个循环,重复③、④两步。
6.观察试验现象
本书各知识点的讲解均基于 STM32F4 系列微控制器和 Keil5 uVision 开发环境。由于开发环
境不支持 STM32F4 系列微控制器的“Logic Analyzer”示波器的软件模拟功能,因此本任务的
观察试验现象环节需要使用硬件示波器来完成。
(1)初始化 PWM 信号波形观察
将示波器“通道 1”和“通道 2”探头分别连接“PE9”和“PE11”引脚,并实现共地,可
观察到应用程序运行时的初始化 PWM 信号波形(CH1 和 CH2),如图 3-3-10 所示。

从图 3-3-10 左下角的参数可以看到,TIM1 被配置为周期 7.14μs,频率 140.1kHz。CH1
初始化 PWM 信号正占空比为 49.93%,CH2 初始化 PWM 信号正占空比为 32.91%。
(2)电机正转时的 PWM 信号波形观察
按下 Key1,控制电机正转,此时观察到的 PWM 信号波形如图 3-3-11 所示。

从图 3-3-11 左下角的参数可以看到,电机正转时,CH1 的 PWM 信号正占空比为 70.03%,
CH2 的 PWM 信号占空比未知。
(3)电机停转时的 PWM 信号波形观察
再次按下 Key1 或者按下 Key2,电机将停转,此时观察到的 PWM 信号波形如图 3-3-12
所示。

 

 从图 3-3-12 左下角的参数可以看到,电机停转时,CH1 和 CH2 的 PWM 信号正占空比均
为 94.10%。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lee达森

创作不易,感谢打赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值