STM32-NUCLEO-F411RE—捕获PWM信号测出占空比和频率
使用STM32CubeMX生产初始化代码,底板硬件使用NUCLEO-F411RE开发板
一、新建工程
二、选择芯片型号
我使用的是STM的NUCLEO开发板 ,在筛选其中填入411
三、配置时钟
首先选择外部晶振: 开发板焊接了外部晶振,所以我 RCC(Reset and Cock Control) 配置选择了 Crystal/Ceramic Resonator(石英/陶瓷谐振器),配置完成后,右边的 Pinout view 里相关引脚就会被标绿。
配置时钟频率: 外部高速时钟配置完成后,进入 Clock Configuration 选项,根据实际情况,将系统时钟配置为 96MHz,最后按下回车,软件会自动调整分频和倍频参数。
四、配置调试模式
ST-Link 就是 Serial Wire 调试模式,一定要设置!!! 以前使用 M0 的芯片,不配置这个模式没出现问题,但现在这个型号,如果不配置 Serial Wire 模式,程序一旦通过 ST-Link 烧录到芯片中,芯片就再也不能被ST-Link 识别了。(后来我是通过 STMISP 工具烧录程序/擦除后才恢复正常的)
五、根据内部原理分析
这里以TIM_CH1为例,当从CH1输入一个PWM波,通过输入滤波后将会产生两路信号:tim_ti1fp1 & tim_ti1fp2,分别送至tim_ic1 & tim_ic2,也就是说一个TI信号将会被映射成两路的IC信号,所以可以通过进行边沿检测来测量PWM的频率以及占空比。
六、配置定时器模式参数
具体步骤如下:
1、设置定时器Slave Mode为Reset Mode,也就是当检测到上升沿时,定时器复位;
2、PWM由CH1进入,触发源设置为TI1FP1,并设置IC1为上升沿捕获;
3、当第一次捕获到上升沿时,定时器复位,计数寄存器CNT清零;
4、当IC2捕获到下降沿时,计数器CNT的值将会被存到捕获寄存器CCR2中;
5、当IC1再次捕获到上升沿时,计数器CNT的值将会被存到捕获寄存器CCR1中,同时将定时器复位;
因此,CCR1的值就是周期,CCR2的值就是占空比。
配置如下:
我们将PA8的TIM1-CH1设置为捕获输入管脚,通道1设置为直接模式,通道2设置为间接模式
预分频系数设为APB总线频率设置为96-1,因为内部是加1操作所以要在这里减1,同样预装载值也要减1操作,为了后面方便计算我这里设置成1000-1
输入捕获能捕获到的最小的频率为 96M / { (ARR+1)x(PSC+1) } 注意,这里的计数周期不能设置的太小,如果我们设置的计数周期 < PWM周期那么就无法捕获PWM脉冲,一般驱动电机的PWM是10k ~ 25kHz,我们设置的周期为1ms,对应频率为1kHz,那么就可以捕获1kHz以上的PWM 信号
而最终计算得到的频率值 = 分频后得到的时钟频率 / 上升沿个数 = ( 定时器时钟频率 / 预分频系数 )/ 上升沿个数 ;
最终计算的到的占空比 = 下降沿个数 / 上升沿个数;
输入捕获需要开启定时器的中断,无论是计时溢出还是输入捕获都需要使用到中断。 配置NVIC(Nested Vector Interrupt Controller):
七、生成 Keil 工程
设置 IDE 和 工程目录及名称: 将每种外设的代码存放到不同的 .c /.h 文件中,便于管理(不然都会被放到 main.c 中)。
八、中断函数写在哪
在使用标准库时,我们是将中断处理写在最底层的中断处理函数中,如 EXTI0_IRQHandler(),但 Hal 库增加了回调函数,将中断底层一些必要的操作 “隐藏” 了起来(如清除中断)。
中断的调用顺序是(以 EXTI0 为例):EXTI0_IRQHandler() —> HAL_GPIO_EXTI_IRQHandler() —> HAL_GPIO_EXTI_Callback()。
TIM2 的中断服务函数已经在 stm32f1xx_it.c 中定义(STM32CubeMX 自动生成的)
void TIM1_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim1);
}
HAL_TIM_IRQHandler() 是 HAL 库的定时器总中断,里面代码很多,这里不展示,我们只需要知道一点——当 TIM2 计数值溢出或发生其他事件(如捕获到上升/下降沿信号)时,系统会执行一系列的中断回调函数,其中包括我们将要用到的 计数溢出回调函数HAL_TIM_PeriodElapsedCallback() 和 输入捕获回调函数HAL_TIM_IC_CaptureCallback()。
九、代码部分
下面是生成 Keil 工程中关于 TIM1(输入PWM捕获)初始化的代码:
#include "tim.h"
TIM_HandleTypeDef htim1;
void MX_TIM1_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 96-1;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1000-1;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(tim_baseHandle->Instance==TIM1)
{
__HAL_RCC_TIM1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(TIM1_CC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM1)
{
__HAL_RCC_TIM1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_8);
HAL_NVIC_DisableIRQ(TIM1_CC_IRQn);
}
}
主函数 工作
__IO uint16_t TIM1_IC2Value = 0;
__IO uint16_t TIM1_IC1Value = 0;
__IO float TIM1_DutyCycle = 0;
__IO float TIM1_Frequency = 0;
MX_TIM1_Init();
开启捕获通道
HAL_TIM_IC_Start_IT (&htim1,TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT (&htim1,TIM_CHANNEL_2);
我们的实验代码的核心部分为中断回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim ->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
TIM1_IC1Value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
TIM1_IC2Value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
if (TIM1_IC1Value != 0)
{
TIM1_DutyCycle = (float)((TIM1_IC2Value+1) * 100) / (TIM1_IC1Value + 1);
TIM1_Frequency = (96000000/(96))/(float)(TIM1_IC1Value + 1);
printf("TIM1_占空比:%0.2f%% TIM1_频率:%0.2fHz\n",TIM1_DutyCycle,TIM1_Frequency);
}
else
{
TIM1_DutyCycle = 0;
TIM1_Frequency = 0;
}
}
}
十、测试部分
|