STM32G474采用TIM8产生方波信号,使用TIM1工作于捕获1模式,并计算方波频率。捕获方波周期,在有些开发中,还是能用到。建议开发时使用HAL库自带的库函数。使用寄存器方法也可以实现,但是后期修改不太方便。
测试时,将PA8引脚复用为TIM1_CH1,LED灯引脚为PC13
TIM1_CH1重映射,见下面的表格:
1、测试程序
#include "Timer1.h"
#include "LED.h"
#include "stm32g4xx_hal.h"
#include "stdio.h" //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
//STM32G474采用TIM8产生方波信号,使用TIM1工作于捕获1模式,并计算方波频率。
//捕获方波周期,在有些开发中,还是能用到。建议开发时使用HAL库自带的库函数。
//使用寄存器方法也可以实现,但是后期修改不太方便.
//将PA8引脚复用为TIM1_CH1
//LED灯引脚为PC13
TIM_HandleTypeDef htim1; //TIM1句柄
double uwTimclock1;
uint32_t uwPrescalerValue1;
uint32_t uwIC2Value1 = 0; //用来保存第1次捕获到的值
uint32_t uwIC2Value2 = 0; //用来保存第2次捕获到的值
uint32_t uwDiffCapture = 0; //两次捕获到的差值
uint16_t uhCaptureIndex = 0; //用来表示捕获下标
float uwFrequency = 0;//用来保存计算到的频率
uint16_t LED_cnt;
void Timer1_Init(void);
void TIM8_Init(void);
void Print_Period(void);
//使用TIM1捕获
void Timer1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
__HAL_RCC_TIM1_CLK_ENABLE(); //使能“定时器1”的时钟,Enable TIM1 clock
__HAL_RCC_GPIOA_CLK_ENABLE(); //GPIOA时钟使能
GPIO_InitStruct.Pin = GPIO_PIN_8; //选择引脚编号8
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; //复用功能推挽模式
GPIO_InitStruct.Pull = GPIO_NOPULL; //引脚上拉和下拉都没有被激活
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚的输出速度为120MHz
GPIO_InitStruct.Alternate = GPIO_AF6_TIM1; //将PA8引脚复用为TIM1_CH1
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
uwTimclock1 = HAL_RCC_GetPCLK2Freq();
//读取PCLK2的时钟频率,Return the PCLK2 frequency
//若PCLK2的分频器值为1,则和SystemCoreClock的值相等
//Frequency computation: for this example TIMx (TIM1) is clocked by APB2Clk
uwPrescalerValue1 = (uint32_t) ((uwTimclock1 / 1000000U) - 1U);
//uwPrescalerValue1=170
htim1.Instance = TIM1;
htim1.Init.Period = 0xFFFF;//在配置为捕获时,定时器周期必须设置为0xFFFF
htim1.Init.Prescaler = uwPrescalerValue1;
//设置TIM1预分频器为uwPrescalerValue1
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
//设置时钟分频系数,TIM1_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
//溢出时间为(FFFF+1)*1*170/170000000/1=65.535ms
//最大误差为170/170000000=1us,则允许采集的最大周期为65.535毫妙,最小周期为1微妙
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.RepetitionCounter = 0;//重复计数(1-0)次,产生1次中断,比较重要
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
//因为该定时器为捕获,因此不使能“自动重装载”
HAL_TIM_IC_Init(&htim1);//TIM1输入捕获初始化
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;//TIMx_EGR.UG位用作产生TIM1_TRGO触发信号
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;//TIMx_EGR.UG位用作产生TIM1_TRGO2触发信号
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;//Master/slave mode is selected
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
//Configures the TIM in master mode.
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;//上升沿捕获
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
//将“TIM输入1,TIM输入2,TIM输入3和TIM输入4”,分别和“IC1、IC2、IC3和IC4”一一对应连接起来
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1);
//因为捕获引脚为TIM1_CH1,所以要配置TIM1捕获1通道
//该函数调用后,会记录htim->Channel=HAL_TIM_ACTIVE_CHANNEL_1
//所以htim要被申请为全局变量;
//TIM_CHANNEL_1表示通道1
sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;//刹车输入引脚BRK处于输入模式
sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
HAL_NVIC_SetPriority(TIM1_CC_IRQn, 0, 0);
//设置NVIC中断分组4:4位抢占优先级,0位响应优先级
//选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
//因为捕获脉宽时间,不能受到其它中断影响产生误差,所以这里设置TIM1中断优先级为0
HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);//使能TIM1产生捕获中断
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
//Start the Input Capture in interrupt mode
//TIM_CHANNEL_1表示通道1
TIM8_Init();
}
//HAL_TIM_IRQHandler()会调用这个函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if(uhCaptureIndex == 0)
{
uwIC2Value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
//保存读到的第1个捕获数据
//Get the 1st Input Capture value
//TIM_CHANNEL_1表示通道1
uhCaptureIndex = 1;
}
else if(uhCaptureIndex == 1)
{
uwIC2Value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
//保存读到的第2个捕获数据
//Get the 2nd Input Capture value
//TIM_CHANNEL_1表示通道1
if (uwIC2Value2 >= uwIC2Value1)
{
uwDiffCapture = (uwIC2Value2 - uwIC2Value1);//计算差值
}
else if (uwIC2Value2 < uwIC2Value1)
{
uwDiffCapture = ( (0xFFFF - uwIC2Value1) + uwIC2Value2 ) + 1;
//计算差值
//0xFFFF is max TIM1_CCRx value
}
uwFrequency = uwTimclock1 /uwPrescalerValue1 / uwDiffCapture;
uhCaptureIndex = 0;//允许再次捕获
}
}
}
//定时器1捕获中断
void TIM1_CC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim1);
}
//用来产生方波信号///
void TIM8_Init(void)
{
TIM_HandleTypeDef htim8; //TIM8句柄
RCC_ClkInitTypeDef clkconfig;
uint32_t uwTimclock8 = 0;
uint32_t pFLatency;
uint32_t uwPrescalerValue8 = 0;
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
__HAL_RCC_TIM8_CLK_ENABLE();//使能“定时器8”的时钟,Enable TIM8 clock
HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);//Get clock configuration
uwTimclock8 = HAL_RCC_GetPCLK2Freq();
//读取PCLK2的时钟频率,Return the PCLK2 frequency
//若PCLK2的分频器值为1,则和SystemCoreClock的值相等
uwPrescalerValue8 = (uint32_t) ((uwTimclock8 / 1000000U) - 1U);
//uwPrescalerValue8=170
htim8.Instance = TIM8;
htim8.Init.Period = (1000000U / 1000U) - 1U;
//定时器周期999
htim8.Init.Prescaler = uwPrescalerValue8;
//设置TIM8预分频器为uwPrescalerValue8
htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
//设置时钟分频系数,TIM8_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
//溢出时间为(999+1)*1*170/170000000/1=1毫秒
htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
htim8.Init.RepetitionCounter = 0;//重复计数(1-0),产生一次中断
htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&htim8);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
//TIM8_TRGO是adc_ext_trg9,用来触发ADC1/2/3/4/5
// sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
//TIM8_TRGO2是adc_ext_trg10,用来触发ADC1/2/3/4/5
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig);
//Configures the TIM in master mode.
HAL_TIM_Base_Start_IT(&htim8);
HAL_NVIC_EnableIRQ(TIM8_UP_IRQn);//使能TIM8产生中断
HAL_NVIC_SetPriority(TIM8_UP_IRQn, 5, 0U);
//设置NVIC中断分组4:4位抢占优先级,0位响应优先级
//选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
//这里设置TIM8中断优先级为5
LED_cnt=0;
}
//TIM8更新中断,每1ms中断一次
//为了节省内存,该中断处理程序使用“寄存器”处理,该死的HAL库,就是这么屌;
void TIM8_UP_IRQHandler(void)
{
if( (TIM8->SR & TIM_FLAG_UPDATE) == TIM_FLAG_UPDATE)
{//读取TIM8状态寄存器TIMx_SR的bit0(UIF),UIF=1表示产生了“TIM8更新事件”
if( (TIM8->DIER & TIM_IT_UPDATE) == TIM_IT_UPDATE )
{//读取TIM8中断使能寄存器TIMx_DIER的bot0(UIE),查看UIE=1?
TIM8->SR = ~(TIM_IT_UPDATE);
LED_cnt++;
if(LED_cnt>25)//每25毫秒,LED灯闪烁一次
{
LED_cnt=0;
LED1_Toggle(); //LED1引脚输出电平翻转
}
}
}
}
void Print_Period(void)
{
float f;
printf("uwDiffCapture=%u\r\n",uwDiffCapture);
f=uwFrequency;
printf("uwFrequency=%0.2fHz\r\n",f);
f=1000/f;
printf("Period=%0.2fms\r\n",f);
}
2、测试结果
3、误差分析:
测试有点误差,LED闪烁为25*2=50ms,实际测试时间偏大,是由于TIM8的中断优先级不是最高级导致的,但测试原理是正确的 。