前言
仅以此篇文章梳理我编写该实例的过程
概述
我手中的正点原子STM32MINI板所用的主控型号为STM32F103C8T6,因此高级定时器只有TIM1和TIM8,本章我所使用的资源为TIM1的CH1,CH1N,BKIN,通过这些资源实现PWM的互补输出以及刹车功能
GPIO的配置
在使用TIM1的CH1,CH1N和BKIN前,让我们先来看下,这些引脚涉及到哪些GPIO以及GPIO需要怎样的配置? 翻开STM32中文参考手册,在GPIO章节-外设的GPIO设置中,我们可以看见以下描述: 从表格中很清楚的看出,由于本次实例我们是需要做输出功能的,因此GPIO的配置为: TIM1_CH1:推挽复用输出 TIM1_CH1N:推挽复用输出 TIM1_BKIN:浮空输入
在了解完GPIO的配置后,我们再看看TIM1_CH1,TIM1_CH1N,TIM1_BKIN在芯片中对应的是哪个GPIO? 翻开数据手册,可以找到如下描述 由于我的MINI板主控型号封装是LQFP64的,所以对于我的MINI板,相对应的引脚为: TIM1_CH1:PA8 TIM1_CH1N:PB13 TIM1_BKIN:PB12 当然,TIM1_CH1也不是仅仅是对应PA8的,具体可以查数据手册-引脚对应
OK,经过以上我们大致了解到TIM1_CH1、TIM1_CH1N、TIM1_BKIN对应的GPIO以及GPIO的配置
相信大家都有一个疑问,我当时也有这个疑问,那就是在数据手册中,以上功能都是复用的,那到底要不要使能AFIO的时钟? 对于这个问题,我查看了参考手册-GPIO-AFIO章节,找到了如下描述: 描述中提到,对AFIO_EVCR,AFIO_MAPR,AFIO_EXTICRX读写前要先打开AFIO时钟,那在本次实例中,我们究竟需不需要对这3个寄存器进行读写操作呢? 由于AFIO_EVCR是事件控制寄存器,AFIO_EXTICRX是外部中断控制寄存器,这两个在本次实例中我们都用不到,因此我们重点来看AFIO_MAPR复用重映射和调试IO寄存器,描述如下: 注意到,AFIO_MAPR复位值为0x0000 0000,而bit[7:6]在00时,PA8对应CH1,PB12对应BKIN,PB13对应CH1N,因此,我们本次实例并不需要对AFIO的寄存器进行读写操作,只使用寄存器默认的配置即可满足我们本次的需求,所以我们不需要打开AFIO的时钟
到这里,我们已经解决了GPIO、GPIO配置以及是否需要打开AFIO时钟的问题,那接下来还需要确定什么呢? 我们都知道要使用一个外设功能,都需要把对应的时钟打开,因此,打开参考手册-系统架构 从上图的架构图来看,TIM1_CH1、TIM1_CH1N和TIM1_BKIN分别复用的PA8、PB13和PB12挂在了APB2总线上,当然TIM1自己也挂在APB2总线上
OK,到这里,我们需要确认的东西都确认完了,那就可以编写代码了,所有GPIO配置代码如下:
static void GPIOConfig(void)
{
GPIO_InitTypeDef GPIOInitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOA, &GPIOInitStruct);
GPIOInitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOB, &GPIOInitStruct);
GPIOInitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_Init(GPIOB, &GPIOInitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
PWM互补输出与刹车功能
为方便后面的记录,我先说清TIM_TimeBaseInitTypeDef、TIM_OCInitTypeDef、TIM_BDTRInitTypeDef这3个结构体都是什么
TIM_TimeBaseInitTypeDef
这个结构体叫时基结构体,就是一个定时器最基本的结构体,高级/通用/基本定时器都有时基结构体,主要用来配置信号周期、计数器频率,计数模式之类的,在库函数中描述如下:
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
TIM_Prescaler:定时器分频系数,用于调整计数器每次计数的时间,计数频率为:fck_psc / (TIM_Prescaler + 1) 通过以上描述,我们知道,要想得到计数器的计数频率CK_CNT,我们需要知道fck_psc的数值,那么这个fck_psc是怎么来的呢? 翻看参考手册-定时器,我们可以找到如下框图: 从上面的定时器框图我们可以看到CK_PSC,来自内部时钟CK_INT,而CK_INT又来自RCC的CK_TIM1和CK_TIM8,那问题来了,这个CK_TIM1和CK_TIM8又是从哪来的? 继续查看参考手册,在参考手册-时钟,我们找到了以下的时钟框图(只截取了部分):
从时钟框图来看,TIMxCLK来自APB2,而APB2来自AHB,而AHB又来自SYSCLK,到这里我们惊奇的发现,若使用内部时钟,fck_psc的数值只有SYSCLK,AHB的分频系数,APB2的分频系数有关,而在一般情况下,AHB分频系数为1,而APB2的分频系数也为1,因此fck_psc的数值实际上为SYSCLK
TIM_CounterMode:计数模式,有向上、向下、中央对齐1、中央对齐2、中央对齐3这5种模式,在底层是对TIMx_CR1寄存器的bit[6:4]操作 TIM_Period:信号周期,从0开始累加,底层是对TIMx_ARR寄存器操作 相信有人和我一样疑惑,这个信号周期是什么? 我们知道,定时就是设定一段时间,那设定的这一段时间就是信号周期/单次计数时间,很简单,因为定时器定时是按计数器来计时的,例子如下: 假如我们需要定时100ms,系统时钟 72M,那我们应该怎么做呢? 首先:TIM_Prescaler = 7200 - 1 此时计数器单次计数频率为:72M / 7200 = 10KHz 此时计数器单次计数时间为:1/10K * 1000 = 100us 其次:TIM_Period = 1000 - 1 此时设定的时间为:计数器单次计数时间 * 1000 = 100us * 1000 = 100ms 因此信号周期的频率为:1 / 100 ms = 10 Hz
TIM_ClockDivision:原文描述是:定时器时钟CK_INT频率与死区发生器以及数字滤波器采样时钟频率分频化,但实际应该是计数器时钟CK_CNT频率才对,这个只有在配置死区和数字滤波时才用得到,底层是对TIMx_CR1寄存器的bit[9:8]操作,不需要时默认为0x0000就可以了 TIM_RepetitionCounter:重复计数器,只有高级和通用定时器才有这个功能,底层是对TIMx_RCR寄存器操作,若开启重复计数器,只有重复计数器递减到0,才会更新事件,这种机制意味着,若开启重复计数器,定时周期应该等于重复计数器的数值乘上信号周期
以上,就是时基结构体的简单描述啦,更详细的可查参考手册
TIM_OCInitTypeDef
这个结构体叫做输出比较结构体,在库函数中描述如下:
typedef struct
{
uint16_t TIM_OCMode;
uint16_t TIM_OutputState;
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse;
uint16_t TIM_OCPolarity;
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
TIM_OCMode:比较输出模式,以CH1为例,在底层是对TIMx_CCMR1寄存器的bit[6:4],共有8种模式,我一般是使用PWM1,具体的模式描述可以看下图或者参考手册 TIM_OutputState和TIM_OutputNState,这两个没什么好说的,就是使能输出,以CH1为例,在底层是对TIMx_CCER寄存器的bit2和bit0操作 TIM_Pulse:占空比,在底层是对TIMx_CCR1寄存器进行操作,原理是把TIMx_CNT的值与TIM_Pulse的值比较并输出信号,具体输出逻辑取决于TIM_OCMode选择的模式 TIM_OCPolarity和TIM_OCNPolarity:选择输出和互补输出有效电平的极性,在底层是对TIMx_CCER寄存器操作。这两个如果设置为1,那有效电平就为高电平;如果设置为0,那有效电平为低电平 TIM_OCIdleState和TIM_OCIdleState:空闲时的电平状态,底层是对TIMx_CR2寄存器操作 我们注意到参考手册中有提示:在同一时刻,输出和互补输出不能同时处于有效电平上,因此在设置空闲时,TIM_OCIdleState和TIM_OCIdleState不能设置成两个都是有效电平 以上,就是TIM_OCInitTypeDef结构体的描述了,有兴趣深入了解的可以查参考手册
TIM_BDTRInitTypeDef
这个结构体主要是配置刹车和死区,我们也叫刹车死区寄存器,只在TIM1和TIM8中用到,该结构体主要是对TIMx_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;
TIM_OSSRState和TIM_OSSIState:关闭状态选择,具体看以下描述 TIM_LOCKLevel:锁定等级,这个也没什么好说的,看以下描述 TIM_DeadTime:死区时间设置,这个计算很简单,高3位决定选用哪条公式,然后再按公式计算就可以了,但是**需要注意Tdts这个的取值取决于时基结构体的TIM_ClockDivision的取值,**详细看上面是时基结构体的相关描述 如果计数器频率为10KHz,TIM_ClockDivision = 1,TIM_DeadTime = 0x80,那死区时间是多少? 首先,1 / 10KHz = 100us,由于TIM_ClockDivision = 1,所以Tdts = Tck_cnt = 100us 其次,0x80高3位是100,因此选用第2条公式,所以死区时间 = 64 x 2 x 100 us = 12.8ms
TIM_Break:刹车功能使能,具体描述如下 TIM_BreakPolarity:刹车极性,当为0时,低电平触发刹车(停止输出),当为1时,高电平触发刹车 TIM_AutomaticOutput:自动输出使能,具体看下图,没什么好说的 以上,就是刹车和死区寄存器的配置说明了,当然在配置完之后,还需要使能主输出后,才会有OC和OCN输出
代码配置
在说了那么多后,我们大致都知道该怎么配置了,那么我就直接把代码贴出来吧
static void TIM1Config(void)
{
TIM_TimeBaseInitTypeDef TIMTimeBaseStruct;
TIM_OCInitTypeDef TIMOCInitStruct;
TIM_BDTRInitTypeDef TIMBDTRInitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIMTimeBaseStruct.TIM_Period = 1000 - 1;
TIMTimeBaseStruct.TIM_Prescaler = 7200 - 1;
TIMTimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIMTimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIMTimeBaseStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIMTimeBaseStruct);
TIMOCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIMOCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIMOCInitStruct.TIM_OutputNState = TIM_OutputNState_Enable;
TIMOCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIMOCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIMOCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Set;
TIMOCInitStruct.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIMOCInitStruct.TIM_Pulse = 250 - 1;
TIM_OC1Init(TIM1,&TIMOCInitStruct);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIMBDTRInitStruct.TIM_OSSRState = TIM_OSSRState_Enable;
TIMBDTRInitStruct.TIM_OSSIState = TIM_OSSIState_Enable;
TIMBDTRInitStruct.TIM_LOCKLevel = TIM_LOCKLevel_1;
TIMBDTRInitStruct.TIM_DeadTime = 0x80;
TIMBDTRInitStruct.TIM_Break = TIM_Break_Enable;
TIMBDTRInitStruct.TIM_BreakPolarity = TIM_BreakPolarity_Low;
TIMBDTRInitStruct.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(TIM1, &TIMBDTRInitStruct);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
PWM互补输出与刹车实例演习
void bspTIMInit(void)
{
GPIOConfig();
TIM1Config();
}
int main(void)
{
bspTIMInit();
while (1);
}
在运行以上代码后,我通过逻辑分析仪抓取的波形如下: 通过以上波形,可见确实是输出了 PWM互补波形,信号周期也是十分接近10Hz
到这里就有人会问,怎么没有死区时间呀,不用着急,我把波形放大给你们看看 从上图看来,确实存在一段死区,但具体时间是多少?逻辑分析仪看不出来,有兴趣的小伙伴可以用示波器抓下,如果没什么问题的话,那应该就是12.8ms,至于我为什么不用示波器抓,那是因为我没有呀
好了,到这,PWM互补输出与刹车功能学习过程就记录完了,不知不觉,居然写了5个小时
|