思路
主要是利用到了STM32的TIM输入捕获功能
将定时器设置为1MHZ的计数频率,定时计数器增加一就是增加1us
- 首先设置为上升沿捕获,捕获上升沿记录此刻的时间计数值;
- 然后切换为下降沿捕获,捕获下降沿记录此刻的时间计数值;
- 最后设置为上升沿捕获,捕获上升沿记录此刻的时间计数值;
对于16bit定时器,最大可计数0xFFFF,也就是65535us,那么:*高电平持续的时间 = 定时器溢出计数 * 0xFFFF + 当前计数值*
对于32bit定时器,最大可计数0xFFFFFFFF,也就是4294.967295s,这个时间足够测量脉冲宽度的了,那么:高电平持续的时间 = 当前计数值
高电平持续的时间 = ② - ①
周期 = ③ - ①
CubeMX以及代码的配置
TIM
Clock Source: 时钟源,这里选择内部时钟
Channl1: 通道功能,这里就选择
Input Capture direct mode
直接输入捕获模式Prescale: 分频这里就选择83,计算下来正好就是1us一次计数
Count Perid: 计数器,这里就设置成最大值就好,由于TIM3是16bits的所以最大就是65335还需要考虑一下定时器溢出的问题,之后可以采用TIM5,那是32bits的计数器,几乎可以不用考虑计数溢出的问题
Polarity Selection: 捕获极性设置,这里选择上升沿捕获
Input Filter: 滤波器系数,这里暂时先不滤波
这里注意开启TIM3的中断,因为之后需要使用输入捕获中断(就是每捕获到一次目标就会进入一次中断)
这里需要将GPIO配置下,将GPIO设置为下拉输入,目的就是为了确保当没有信号输入的时候IO口的电平保持稳定
代码部分
在tim.c中
/* USER CODE BEGIN 0 */
__IO uint32_t TIM3_TIMEOUT_COUNT = 0; ///< 定时器3定时溢出计数
uint32_t TIM3_CAPTURE_BUF[3] = {0, 0, 0}; ///< 分别存储上升沿计数、下降沿计数、下个上升沿计数
__IO uint8_t TIM3_CAPTURE_STA = 0xFF; ///< 状态标记,默认是不测量的状态
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
/**
* 设置TIM3输入捕获极性
* @param TIM_ICPolarity:
* TIM_INPUTCHANNELPOLARITY_RISING :上升沿捕获
* TIM_INPUTCHANNELPOLARITY_FALLING :下降沿捕获
* TIM_INPUTCHANNELPOLARITY_BOTHEDGE:上升沿和下降沿都捕获
*/
static inline void TIM3_SetCapturePolarity(uint32_t TIM_ICPolarity)
{
htim3.Instance->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
htim3.Instance->CCER |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP));
}
// 定时器3时间溢出回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim3.Instance)
{
TIM3_TIMEOUT_COUNT++; // 溢出次数计数
}
}
///< 输入捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim3.Instance)
{
switch (TIM3_CAPTURE_STA)
{
case 1:
{
//printf("准备捕获下降沿...\r\n");
TIM3_CAPTURE_BUF[0] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + TIM3_TIMEOUT_COUNT * 0xFFFF;
TIM3_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_FALLING); // 设置为下降沿触发
TIM3_CAPTURE_STA++;
break;
}
case 2:
{
//printf("准备捕获下个上升沿...\r\n");
TIM3_CAPTURE_BUF[1] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + TIM3_TIMEOUT_COUNT * 0xFFFF;
TIM3_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_RISING); // 设置为上升沿触发
TIM3_CAPTURE_STA++;
break;
}
case 3:
{
//printf("捕获结束...\r\n");
//printf("# end ----------------------------------------------------\r\n");
TIM3_CAPTURE_BUF[2] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + TIM3_TIMEOUT_COUNT * 0xFFFF;
HAL_TIM_IC_Stop_IT(htim, TIM_CHANNEL_1); // 停止捕获
HAL_TIM_Base_Stop_IT(&htim3); // 停止定时器更新中断
TIM3_CAPTURE_STA++;
break;
}
default:
break;
}
}
}
//< TIM3轮训状态切换
void TIM3_Poll(void)
{
switch (TIM3_CAPTURE_STA)
{
case 0:
{
//printf("# start ----------------------------------------------------\r\n");
//printf("准备捕获上升沿...\r\n");
TIM3_TIMEOUT_COUNT = 0;
__HAL_TIM_SET_COUNTER(&htim3, 0); // 清除定时器2现有计数
memset(TIM3_CAPTURE_BUF, 0, sizeof(TIM3_CAPTURE_BUF)); // 清除捕获计数
TIM3_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_RISING); // 设置为上升沿触发
HAL_TIM_Base_Start_IT(&htim3); // 启动定时器更新中断
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); // 启动捕获中断
TIM3_CAPTURE_STA++;
break;
}
case 4:
{
uint32_t high = TIM3_CAPTURE_BUF[1] - TIM3_CAPTURE_BUF[0];
uint32_t cycle = TIM3_CAPTURE_BUF[2] - TIM3_CAPTURE_BUF[0];
double frq = 1.0 / (((double)cycle) / 1000000.0);
TIM3_CAPTURE_STA++;
printf("\r\n\r\n");
printf("################################# START #########################################\r\n");
printf("高电平持续时间:%dms\r\n", high / 1000);
printf("周期 :%dms\r\n", cycle / 1000);
printf("频率 :%fHz\r\n", frq);
printf("################################## END ######################################\r\n\r\n");
break;
}
default:
break;
}
}
/* USER CODE END 1 */
基本思路就是
为了在之后的捕获中断中改变捕获的极性但是HAL库是通过配置结构体进而配置寄存器来实现的,这里最好自己先定义一个函数,函数中就是
TIM3_SetCapturePolarity(uint32_t TIM_ICPolarity)
/** * 设置TIM3输入捕获极性 * @param TIM_ICPolarity: * TIM_INPUTCHANNELPOLARITY_RISING :上升沿捕获 * TIM_INPUTCHANNELPOLARITY_FALLING :下降沿捕获 * TIM_INPUTCHANNELPOLARITY_BOTHEDGE:上升沿和下降沿都捕获 */ static inline void TIM3_SetCapturePolarity(uint32_t TIM_ICPolarity) { htim3.Instance->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP); htim3.Instance->CCER |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP)); }
在输入捕获中断中分三步来实现
- 第一次捕获到上升沿后将此时的计数器的值存入
TIM3_CAPTURE_BUF[0]
并改变捕获极性(下降沿捕获) - 捕获到下降沿后将此时的计数器的值存入
TIM3_CAPTURE_BUF[1]
(此时的TIM3_CAPTURE_BUF[1]-TIM3_CAPTURE_BUF[0]的值就是信号高电平的时间了),再次改变捕获极性(上升沿捕获) - 第二次捕获到上升沿后将此时的计数器的值存入
TIM3_CAPTURE_BUF[2]
(此时TIM3_CAPTURE_BUF[2]-TIM3_CAPTURE_BUF[0]就是信号的周期时间了)
- 第一次捕获到上升沿后将此时的计数器的值存入
建立一个捕获开启函数(状态机)
首先判断
TIM3_CAPTURE_STA
即是频率测量是否开启状态位,同时也是状态机的步骤位开启后需要清零
TIM3_TIMEOUT_COUNT
计数器变量的值清零
TIM3_TIMEOUT_COUNT
计数器变量,清零TIM3现有的计数__HAL_TIM_SET_COUNTER(&htim3, 0);
各个状态和标记位,清零捕获计数的数组memset(TIM3_CAPTURE_BUF, 0, sizeof(TIM3_CAPTURE_BUF));
,设置为TIM3_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_RISING);
上升沿触发,HAL_TIM_Base_Start_IT(&htim3);
启动定时器更新中断,HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
启动捕获中断。
执行完以上步骤后
TIM3_CAPTURE_STA
加1,进而会执行捕获中断中的程序待到
TIM3_CAPTURE_STA
置4后(即是已经完成了周期的测量),计算出频率值与高电平持续时间,占空比即可
最终结果
这是一个250kHz的方波信号
通过串口输出结果
一些总结
在利用TIM输入捕获测高电平时间时,因为频繁的在进入捕获中断所以不能有任何中断或者其他事件干扰这个过程。!!比如说不能利用串口发送数据等,总之不能通过CPU再进行其他的一系列操作,只能等待这个捕获过程完成后才可进行其他操作,所以我这里是使用串口发送特定字符使测频程序启动的。
这也是这个TIM输入捕获测频率的一个缺点——占用CPU的频率太高了…不过在没有干扰的情况下测量得到的频率准确性很高几乎没有误差,最高可以测250kHz的方波输出
利用状态机的思维来编程,即是设置轮询步骤位,根据不同的步骤位来进行不同的操作,这种状态机思维常用于某个功能需要多外设或者多模块函数按照特定的顺序来共同完成,通过设置不同模块对应的步骤位可以协调这些模块按步骤顺序来执行。
当需要开启系统中的某个功能时,只需要将步骤位置为相应的起始位,各个模块就可以按照相应的顺序执行。并在最后将步骤位置”END”状态,方便我们在下次需要再次使用带该功能时再将步骤位置起始位。
利用到的函数
__HAL_TIM_SET_COUNTER( 定时器地址 , 设置的数值 );//设置某定时器的计数器值 TIM2_SetCapturePolarity(捕获触发模式);//设置输入捕获的触发方式 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1)//从捕获比较单元读出当前的捕获值