PWM频率 = 更新频率 = 72M / (PSC – 1) / (ARR – 1)

占空比 = CCR / (ARR + 1)

所以通过PSC调节频率,不会影响占空比,会比较方便

设定ARR为100 – 1不变只改变PSC来调节频率,而且ARR为此值时,CCR的数值直接就是占空比 ,用起来比较直观

一般我们可以根据分辨率的要求,先确定好ARR,再用PSC决定频率,CCR决定占空比。

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);

单独写入PSC的函数,TIM_PSCReloadMode可以选择时更新事件重装还是立即重装,也就是影子寄存器

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);

OCInit四个通道都是分开来的,需要单独操控

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

而ICInit是4各通道共用一个函数,在结构体中有一个参数,可以用来选择具体配置哪个通道,因为可能有交叉通道的配置,所以函数合在一起比较方便

void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

这个函数和上一个函数类似,都是用于初始化输入捕获单元的,但是上一个函数是只是单一地配置一个通道,而这个函数,可以快速配置两个通道,把外设结构配置成上图二中展示的PWMI模式。

void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

可以给输入捕获的结构体赋一个初始值。

void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

选择输入触发源TRGI,这里就对应了上图三的从模式触发源选择

void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

选择输出触发源TRGO,对应上图三的选择主模式输出的触发源

void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);

选择从模式对应上图三的模式选择部分

void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);

分别用于单独配置通道1,2,3,4的分频器,这个参数结构体里也能够配置,效果一样

uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

分别读取4个通道的CCR与之前的SetCompare函数对应的,读写的都是CCR寄存器,输出比较模式下,CCR只写,要用SetCompare写入,输入捕获模式下,CCR只读,要用GetCapture读取。

 

我们要用TIM2来输出PWM,再用TIM3来捕获。

首先根据图一的顺序来还是GPIO和时基单元的初始化

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//查表可得TIM3的捕获通道1对应的是PA6口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

TIM_InternalClockConfig(TIM3);

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 – 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 – 1;//PSC决定测周法的标准频率fc(72M / 72 = 1MHz)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

接着是输入捕获单元的初始化:

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//因为配置IC初始化的函数只有一个,需要选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//一般滤波器的采样频率都会远高于信号频率,所以只会滤除高频噪声使信号更平滑
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//选择上升沿触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//选择不分频,另外DIV2是二分频,其余DIV4,8是4,8分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择直连通道
TIM_ICInit(TIM3, &TIM_ICInitStructure);

TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);//配置TRGI的触发源为TI1FP1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);//配置从模式为Reset以清零CNT计数器

TIM_Cmd(TIM3, ENABLE);

捕获电路里的分频器,不分频就是每次触发都有效,2分频就是每隔一次有效一次,以此类推

最后再来一个读取CCR寄存器的函数,就能够读取频率了:

uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);//这里因为有误差为了得到整数调整加一
}

最后主函数选择好输出的频率,再在TIM3捕获通道读取CCR即可捕获TIM2输出的频率:

#include “stm32f10x.h”                  // Device header
#include “Delay.h”
#include “OLED.h”
#include “PWM.h”
#include “IC.h”
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();
OLED_ShowString(1, 1, “Freq:00000Hz”);
PWM_SetPrescaler(720 – 1);// Freq = 72M / (PSC + 1) / (ARR + 1) = 72M / 720 / 100 = 1KHz
PWM_SetCompare1(50);// Duty = CCR / 100 = 50%
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);
}
}
然后是PWMI模式测试频率和占空比,和上面的写法差不多,只是多了一个测占空比
STM公司很贴心地设计了一个PWMI模式,可以直接配置好第二个通道,也就是下列最后一行
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//因为配置IC初始化的函数只有一个,需要选择通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//一般滤波器的采样频率都会远高于信号频率,所以只会滤除高频噪声使信号更平滑
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//选择上升沿触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//选择部分品,另外DIV2是二分频,其余DIV4,8是4,8分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择直连通道
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);//在函数中,会自动把剩下一个通道初始化成相反的配置,如这里会配置成交叉通道,下降沿,该通道只支持通道一和通道二的配置
再多加一个计算占空比的函数即可
uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) + 1) * 100/ (TIM_GetCapture1(TIM3) + 1);//CCR2 / CCR1 = 占空比
}
因为CCR2是下降沿触发,而上升沿时计数器会将数值放入CCR1并清零,所以CCR2放的就是高电平的时间,高电平时间除以总时间即使占空比了。
最后就是主函数:
#include “stm32f10x.h”                  // Device header
#include “Delay.h”
#include “OLED.h”
#include “PWM.h”
#include “IC.h”
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();
OLED_ShowString(1, 1, “Freq:00000Hz”);
OLED_ShowString(2, 1, “Duty:00%”);
PWM_SetPrescaler(7200 – 1);// Freq = 72M / (PSC + 1) / (ARR + 1) = 72M / 720 / 100 = 1KHz
PWM_SetCompare1(70);// Duty = CCR / 100 = 50%
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);
OLED_ShowNum(2, 6, IC_GetDuty(), 2);
}
}