(一)TIM通用定时器
在本节的学习中,涉及的知识点很多,大家可以搭配《STM32F10X-中文参考手册》来看,尤其是寄存器说明的部分,希望能对大家有所帮助。
1.定时器分类
STM32F1 系列中,除了互联型的产品,共有 8个定时器,分为基本定时器,通用定时器和高级定时器。 基本定时器 TIM6 和 TIM7 是一个 16位的只能向上计数的定时器,只能定时,没有外部 IO。 通用定时器 TIM2/3/4/5是一个 16位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉, 每个定时器有四个外部IO。高级定时器 TIM1/8是一个 16位的可以向上/下计数的定时器,可以定时,可以输出比较, 可以输入捕捉,还可以有三相无刷电机互补输出信号,每个定时器有8 个外部 IO。
2.通用定时器功能框图
通用定时器功能框图包含了通用定时器最核心内容,掌握了功能框图,对通用定时器就有一个整体的把握,在编程时思路就非常清晰。图中有些寄存 器是带影子的,表示其有影子寄存器。
3.通用定时器功能描述
通用定时器功能复杂,包括TIMx (TIM2、TIM3、TIM4和TIM5),我们将根据通用定时器功能框图,徐徐展开对通用定时器的学习。
3.1时基单元(图中部分1)
可编程通用定时器的主要部分是一个16位计数器和与其相关的自动装载寄存器。这个计数器可 以向上计数、向下计数或者向上向下双向计数。此计数器时钟由预分频器分频得到。 计数器、自动装载寄存器和预分频器寄存器可以由软件读写,在计数器运行时仍可以读写。
时基单元包含:
● 计数器寄存器(TIMx_CNT)
● 预分频器寄存器 (TIMx_PSC)
● 自动装载寄存器 (TIMx_ARR)
自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。计数器由预分频器的时钟输出CK_CNT驱动,仅当设置了计数器TIMx_CR1寄存器中的计数器使 能位(CEN)时,CK_CNT才有效。
3.1.1预分频器(PSC):
预分频器可以将计数器的时钟频率按1到65536之间的任意值分频。它是基于一个(在TIMx_PSC 寄存器中的)16位寄存器控制的16位计数器。这个控制寄存器带有缓冲器,它能够在工作时被改变。新的预分频器参数在下一次更新事件到来时被采用。有一个输入时钟CK_PSC和一个输出时钟CK_CNT。输入时钟CK_PSC就是上面时钟源的输出,输出CK_CNT则用来驱动计数器CNT计数。 通过设置预分频器PSC的值可以得到不同的CK_CNT, 实际计算为:fCK_CNT=fCK_PSC/(PSC[15:0]+1),可以实现1至65536分频。(加1是因为计数从0开始)
3.1.2计数器模式(CNT)
(1) 递增计数模式下,计数器从0开始计数,每来一个CK_CNT脉冲计数器就增加1,直到计数器的值与自动重载寄存器ARR值相等, 然后计数器又从0开始计数并生成计数器上溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成上溢事件就马上生成更新事件(UEV); 如果使能重复计数器,每生成一次上溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。
(2) 递减计数模式下,计数器从自动重载寄存器ARR值开始计数,每来一个CK_CNT脉冲计数器就减1,直到计数器值为0, 然后计数器又从自动重载寄存器ARR值开始递减计数并生成计数器下溢事件,计数器总是如此循环计数。如果禁用重复计数器, 在计数器生成下溢事件就马上生成更新事件;如果使能重复计数器,每生成一次下溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。
(3) 中心对齐模式下,计数器从0开始递增计数,直到计数值等于(ARR-1)值生成计数器上溢事件, 然后从ARR值开始递减计数直到1生成计数器下溢事件。然后又从0开始计数,如此循环。每次发生计数器上溢和下溢事件都会生成更新事件。
3.1.3自动重载寄存器(ARR)
自动重载寄存器ARR用来存放与计数器CNT比较的值,如果两个值相等就递减重复计数器。可以通过TIMx_CR1寄存器的ARPE位控制自动重载影子寄存器功能, 如果ARPE位置1,自动重载影子寄存器有效,只有在事件更新时才把TIMx_ARR值赋给影子寄存器。如果ARPE位为0,则修改TIMx_ARR值马上有效。
3.1.4实际单元初始化程序
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的时基单元
(1)时钟分频配置
(2)计数模式
(3)预分频设置(PSC)
3.2输入捕获
输入捕获可以对输入的信号的上升沿、下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽,和测量PWM输入信号的频率和占空比这两种。
输入捕获的大概的原理就是,当捕获到信号的跳变沿的时候,把计数器CNT的值存到捕获寄存器CCR中,把前后两次捕获到的CCR寄存器中的值相减, 就可以算出脉宽或者频率。
3.2.1输入通道
信号需要从定时器的外部引脚TIMx_CH1/2/3/4输入。
3.2.2输入滤波器和边沿检测器
输入滤波器可以过滤掉信号的抖动干扰,当存在干扰时我们需要对输入信号进行重新采样,根据取样定律,需要采样频率大于2倍的输入信号的频率。
滤波器的配置由CR1寄存器的位CKD[1:0]和CCMR1/2的位ICxF[3:0]控制。从ICxF位的描述可知, 采样频率fSAMPLE可以由fCK_INT和fDTS分频后的时钟提供, 其中是fCK_INT内部时钟,fDTS是fCK_INT经过分频后得到的频率, 分频因子由CKD[1:0]决定,可以是不分频,2分频或者是4分频。
边沿检测器用来设置信号在捕获的时候是什么边沿有效,可以是上升沿,下降沿,或者是双边沿,具体的由CCER寄存器的位CCxP和CCxNP决定。
3.2.3捕获通道
捕获通道就是图中的IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器CCR1/2/3/4,当发生捕获的时候,计数器CNT的值就会被锁存到捕获寄存器中。
根据上图我们可以发现,每一个输入通道的信号都可以输入给两个输出通道。例如输入通道TIMx_CH1的信号通过TI1FP1和TI1FP2可以进入到捕获通道IC1和IC2。这就是我们将会介绍的PWM捕获。输入通道和捕获通道的映射关系具体由寄存器CCMRx的位CCxS[1:0]配置。
3.3输出比较
输出比较就是通过定时器的外部引脚对外输出控制信号,有冻结、将通道设置为匹配时输出有效电平、 将通道设置为匹配时输出无效电平、翻转、强制变为无效电平、强制变为有效电平、PWM1和PWM2这八种模式。其中PWM使用的最多。
在初始化输出比较单元时,我们需要根据GPIO口选择输出比较通道,具体的对应关系和重映射可以查表了解。
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
在高级定时器中还有死区设置,一般通用定时器也够用了,这里我们就不再介绍了。
(二)PWM实验
1.PWM输入模式
PWM输入模式是输入捕获的特例,只能使用通道1和通道2,需要占用两个捕获寄存器。
当使用PWM输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入的时候最多只能使用两个输入通道(TIx)。
例如,在信号进入TI1通道时,信号会被分为两路,一路是TI1FP1,另外一路是TI1FP2。其中一路为频率,一路为占空比,程序中作为触发输入的为频率,另一路即为占空比。作为触发输入的需要设置极性,一路设置好极性后,另一路会自动设置为相反的极性。
2.PWM输出模式
PWM输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器ARR的值决定,占空比由比较寄存器CCR的值决定。占空比Duty = CCR / (ARR + 1)。
CCR和ARR共同决定占空比,函数TIM_SetCompare"x"仅设置CCR的值。
PWM模式分为两种,PWM1和PWM2。
根据计数器CNT计数的方向不同还分为边沿对齐模式和中心对齐模式。 一般的电机控制用的都是边沿对齐模式,FOC电机一般用中心对齐模式。
/*输出比较初始化*/
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
3.具体步骤
(1)开启时钟
(2)GPIO初始化
(2)配置时钟源
(4)时基单元初始化
(5)输出比较初始化 在这里CCR初始值设置为0即可,注意要选择正确的输出比较通道,如果需要的话可以进行重映射,不要使得引脚冲突。
(6)TIM使能 TIM_Cmd
(7)定义PWM_SetCompare3函数,以便设置CCR的值
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
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);
//GPIO_SetBits(GPIOA, GPIO_Pin_0);
TIM_InternalClockConfig(TIM2);
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; //PCR
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
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_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2,Compare);
}
以上为PWM模块,接下来,我们将CCR的值写入电机驱动模块,通过控制占空比,调节电机速度,需要注意的是,要配置好GPIO引脚的高低电平来控制电机正转反转,千万不要配置反了,还有,PWM的值只能为正值,有需要时加“-”。
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只能给正数
}
}
这样当输入speed的值时,电机就可以转动了。