由于没有信号生成器,所以我是自己在引脚PA6生成特定的频率和占空比,再通过PA0读出频率和占空比。(有条件的同学可以拿信号发生器试试)
接线图
频率生成
前面有讲到PWM初始化配置的代码和函数,下面是原文链接,只需要更改一下GPIO口和定时器通道,再加上改变PSC的库函数就可以。
原文链接:https://blog.csdn.net/m0_74246768/article/details/139050032
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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 = 720 - 1; //PSC
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_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
void PWM_SetPrescaler(uint16_t Prescaler)
{
TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
}
这里补充一下改变PSC值的库函数。
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
作用是单独写预分频值(PSC)/单独改变PSC的值。
我代码中设置的是1KHz频率,大家可以根据下面的公式自行调整参数进行频率设置。
PWM相关公式
输入捕获配置
初始化配置
1.RCC开启时钟(打开GPIO和TIM的时钟打开)
(1)void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
(2)void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
根据外设挂载的总线选择使用的函数。
2.GPIO初始化,将GPIO配置为输入模式(一般选择上拉输入或浮空输入)
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
作用:配置GPIO初始化
3.配置时基单元(使CNT在内部时钟的驱动下自增运行)
这一部分前面文章有写,不懂的可以参考一下上一篇的内容,此处不多赘述了。
原文链接:https://blog.csdn.net/m0_74246768/article/details/139048136
4.配置输入捕获单元(包括滤波器、极性、分频器等参数)
(1)void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
作用:用结构体配置输入捕获单元的函数
TIMx:选择哪个定时器
TIM_ICInitStruct:包含各个配置的结构体
补充:输入捕获和输出比较都有四个通道,前面文章有写到输出比较的四个通道的库函数,有4个。而输入捕获只有一个函数,是4个通道共用一个函数,在结构体内会额外有个参数去选择配置哪个通道
(2)void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
和上一个函数一样都是初始化输入捕获单元的,上一个函数只能配置一个通道,而这个函数可以快速配置两个通道,把外设结构配置成PWMI模式,这个模式我们下一篇会讲到,这里先不细说。
两个函数二选一就可以。这篇文章我们使用的是TIM_ICInit。
5.选择从模式的触发源(触发源选择为TI1FP1,调用一个库函数后直接给一个参数就行了)
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
作用:选择输入触发源TRGI。
6.选择触发之后执行的操作(执行Reset操作,直接调用库函数即可)
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
作用:选择从模式。
7.调用TIM_Cmd函数定时器。
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
作用:启动定时器。
配置好后,当我们需要读取最新一个周期的频率时,直接读取CCR寄存器,按照fc/N(测周法)计算一下即可。
获取输入频率
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);
作用:分别读取四个通道的CCR。和下面的TIM_SetCompare函数作用是一样的,都是读取CCR寄存器。输入比较模式下,CCR是只写,用TIM_SetCompare写入;输入捕获模式下,CCR只读,要用TIM_GetCapture读出。
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
IC.c
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
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;
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; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//配置通道
TIM_ICInitStructure.TIM_ICFilter = 0xF;//配置滤波器,数越大,滤波效果越好(每个数对应的采样频率和采样次数在参考手册里有讲)
//滤波器计次不会改变信号的原有频率
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//极性选择
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;//分频器
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//选择触发信号从哪个引脚输入
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
TIM_Cmd(TIM3, ENABLE);
}
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
//1000000是1MHz,PSC为72-1,根据公式计算可得出72Mhz/72/CCR+1=1MHz/CCR
}
IC.h
#ifndef __IC_H
#define __IC_H
void IC_Init(void);
uint32_t IC_GetFreq(void);
#endif
初始化输入捕获单元
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);
作用:给输入捕获结构体赋初始值。
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);
作用:选择主模式输出触发源TRGO.。
例题:输入捕获测量频率
通过PWM模块将待测信号输出到PA0,PA0通过导线输入到PA6,PA6是TIM3的通道1,通道1通过输入捕获模块测量得到频率,在主循环中不断刷新显示频率,用OLED显示频率。
main.c
#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) / 100
PWM_SetCompare1(50); //Duty = CCR / 100
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);
}
}
其他重要库函数
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的分频器。(这个参数结构体里面也可以配置,效果是一样的)
测量频率的三种方法
这里补充一下频率测量的三种方法:
当被测频率小于中界频率时,使用测周法。
当被测频率大于中界频率时,使用测频法 。
小C是这么说的,不过看起来不好理解。我看网课是这么理解的。
测频法就是相当于在1s时间内对上升沿计次(每来一个上升沿,其实就是来了几个周期),所以在1s时间内,来了多少上升沿,就是多少个周期,那它的频率也就是多少Hz (频率的定义是1s内出现了多少个周期,频率就是多少Hz)如果是2s的闸门时间,就是计次值除2,;0.5s就是计次值乘2.
测周法的基本原理是周期的倒数是频率,我们测出一个周期的时间,然后取个倒数就是频率,方法是捕获信号的两个上升沿,测量一下两个上升沿之间持续的时间(测量时间的方法实际上也是定时器计次)
使用已知的标准频率fc的计次时钟来驱动计数器,从一个上升沿开始计,计数器从0开始一直记到下一个上升沿停止,计一个数的时间是1/fc,计N个数就是N/fc,再取个倒数就是频率。
把测周法和测频法的N提取出来,令两个N相等(也就是令他们误差相等),把fx求出来,就能得到中界频率的公式。
此处如果有不懂可以去B站搜江科大的32课程,讲的非常详细。