捕获信号的频率其实有很多中实现方式,外部中断、输入捕获、使用外部时钟计数器等。对STM32有一定了解的朋友们在测量频率的问题上往往都会想到利用输入捕获,输入捕获的方式在中低频率段(1HZ-200KHZ)的测量还是比较准确的。在高频段还是建议采用外部时钟计数器的方式来实现。
采用输入捕获实现频率测量的实现步骤如下:
代码实现的部分主要是在输入捕获中断中对捕获的不同溢出情况进行了处理,实测效果还是不错的,4通道同时测的误差也就在1%左右。
宏定义&变量定义部分:
/* IC capture edge define*/
#define IC_RISE_EDGE_1 0
#define IC_RISE_EDGE_2 1
/* Capture TIMER Prescaler and Period define */
#define TIM1_PRESCALER 168-1
#define TIM1_PERIOD 0xFFFF
/* IC capture Parameters define*/
#define TIM1_IC_FREQ 168000000/TIM1_PRESCALER
#define TIM_ICOF_MAX 0x32
static float g_TIM1IcFreq = TIM1_IC_FREQ;
static float g_TIM1Period = TIM1_PERIOD;
static float g_FbFreqBuff[SPD_FB_CHAN_NUM] = {0.0}; /* 频率反馈值*/
static float g_DevFbRotateSpdBuff[SPD_FB_CHAN_NUM] = {0.0}; /* 转速反馈值*/
uint16_t g_IcofCntBuff[TIM_IC_MAX] = {0}; /* 定时器溢出计数值*/
/* 定时器捕获通道枚举定义*/
typedef enum {
TIM1_IC1 = 0,
TIM1_IC2 ,
TIM1_IC3 ,
TIM1_IC4 ,
TIM_IC_MAX ,
} TIM_IC_Typedef;
定时器初始化部分:
void MX_TIM1_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
TIMER1_Handler.Instance = TIM1;
TIMER1_Handler.Init.Prescaler = TIM1_PRESCALER; //168M/168 = 1M计数频率,1us计数一次;
TIMER1_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIMER1_Handler.Init.Period = TIM1_PERIOD; //溢出时间,65.5ms
TIMER1_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIMER1_Handler.Init.RepetitionCounter = 0;
TIMER1_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_IC_Init(&TIMER1_Handler) != HAL_OK) {
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&TIMER1_Handler, &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(&TIMER1_Handler, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) {
Error_Handler();
}
if (HAL_TIM_IC_ConfigChannel(&TIMER1_Handler, &sConfigIC, TIM_CHANNEL_2) != HAL_OK) {
Error_Handler();
}
if (HAL_TIM_IC_ConfigChannel(&TIMER1_Handler, &sConfigIC, TIM_CHANNEL_3) != HAL_OK) {
Error_Handler();
}
if (HAL_TIM_IC_ConfigChannel(&TIMER1_Handler, &sConfigIC, TIM_CHANNEL_4) != HAL_OK) {
Error_Handler();
}
/*## Start the Input Capture in interrupt mode ##########################*/
HAL_TIM_IC_Start_IT(&TIMER1_Handler,TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&TIMER1_Handler,TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&TIMER1_Handler,TIM_CHANNEL_3);
HAL_TIM_IC_Start_IT(&TIMER1_Handler,TIM_CHANNEL_4);
__HAL_TIM_ENABLE_IT(&TIMER1_Handler,TIM_IT_UPDATE);//更新中断用于溢出计数
}
输入捕获回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM1) {
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1) {
TIM1CaptureChannel1Callback();
} else if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2) {
TIM1CaptureChannel2Callback();
} else if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_3) {
TIM1CaptureChannel3Callback();
} else {
TIM1CaptureChannel4Callback();
}
}
}
通道1的捕获处理代码:
/**************************************************************************************************
* @brief :Set PMP01 Control Speed Percent,voltage Control.
* @param :none.
* @retval :none.
* @note :0xFFFF is the Timer1 Period value set in the MX_TIM1_Init().
* Input capture frequency range:1HZ~100KHZ(best)
**/
void TIM1CaptureChannel1Callback(void)
{
static uint8_t ic_edge = IC_RISE_EDGE_1;
static uint32_t cap_val1 = 0;
static uint32_t cap_val2 = 0;
static uint32_t cap_sum = 0;
if(ic_edge == IC_RISE_EDGE_1){
ResetTimerIcofCnt(TIM1_IC1);
cap_val1=__HAL_TIM_GET_COMPARE(&TIMER1_Handler, TIM_CHANNEL_1);
ic_edge = IC_RISE_EDGE_2;
}else{
cap_val2=__HAL_TIM_GET_COMPARE(&TIMER1_Handler, TIM_CHANNEL_1);
if(g_IcofCntBuff[TIM1_IC1] == 0){
cap_sum = cap_val2 - cap_val1;
}else if(g_IcofCntBuff[TIM1_IC1] == 1){//溢出一个计数周期
cap_sum = (g_TIM1Period - cap_val1) + cap_val2;
}else{//溢出N个计数周期
cap_sum = (g_TIM1Period - cap_val1) + g_TIM1Period *(g_IcofCntBuff[TIM1_IC1]-1) +cap_val2;
}
ProcessIcValue(TIM1_IC1,cap_sum);
ic_edge = IC_RISE_EDGE_1;
}
}
其它三个通道都大同小异,就不全贴出来了...
定时器溢出计数函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM1) {
TIM1_IcOverflowCntCallback();
}
}
/**************************************************************************************************
* @brief :TIM1 Input capture overflow count callback function.
* @param :none.
* @retval :none.
* @note :0xFF 为避免出现捕获到周期过长的异常输出(只捕获到一个上升沿、输入波形中断等)
**/
void TIM1_IcOverflowCntCallback(void)
{
for(uint8_t i=0;i<4;i++){
if(g_IcofCntBuff[i] < TIM_ICOF_MAX){
g_IcofCntBuff[i]++;
}else{
g_IcofCntBuff[i]=0;
g_FbFreqBuff[i] = 0;
}
}
}
频率&转速计算:
/**************************************************************************************************
* @brief :Process the Input capture value function.
* @param :none.
* @retval :none.
* @note :
**/
static void ProcessIcValue(TIM_IC_Typedef ic_id,uint32_t _icCnt)
{
g_FbFreqBuff[ic_id] = CalcTheFreq(_icCnt);
g_DevFbRotateSpdBuff[ic_id] = CalcTheRpm(g_FbFreqBuff[ic_id]);
}
/**************************************************************************************************
* @brief :Calc the frequency by the capture count.
* @param :none.
* @retval :none.
* @note :频率计算公式:f=1/T,定时器的输入捕获的计数的频率为1MHZ,两次捕获上升沿的差值sum为计数器
CNT计的次数,所以总的周期即为T=1us*sum,所以频率就fq=1000000/sum HZ.
电机转速与频率的公式:n=60f/p,
n--电机的转速(转/分);
60--每分钟(秒);
f--电源频率(赫兹);
p--电机旋转磁场的极对数(电机也分为分2极,4级,6极,8极等);这里按2极来算
**/
static float CalcTheFreq(uint32_t _icValue)
{
return g_TIM1IcFreq / _icValue;
}
static float CalcTheRpm(float _freq)
{
// return (60 * _freq / 2);
return (30 * _freq); //由上式简化而来
}
最后再提一下在开篇提到的采用外部时钟计数器方式实现频率信号测量的实现思路,此方式测量高频段信号频率准确率不错,也会大大减少中断次数。
采用外部时钟计数器的方式实现思路:
思路是配置两个定时器,定时器a设置为外部时钟计数器模式,定时器b设置为定时器(比如50ms溢出一次,也可以用软件定时器),然后定时器b中断函数中统计定时器a在这段时间内的增量,简单计算即可。
最后放一个通道测试的gif:
完结撒花~~~