文章目录

  • 1. 所用硬件
  • 2. 生成工程
  • 2.1. 创建工程选择主控
  • 2.2. 系统配置
  • 2.3. 配置工程目录
  • 3. 定时器配置(三选一)
  • 3.1. 定时功能
  • 3.2. PWM 输出
  • 3.3. 输入捕获

1. 所用硬件

正点原子Mini板,主控 STM32F103RCT6.

定时器简介
这里主要讨论通用定时器(系统嘀嗒定时器、看门狗定时器、RTC定时器不考虑在内)

对于STM32F103RCT6 单片机:

  • 2个基本定时器。分别是TIM6TIM7。只能16位向上计数、没有IO口,没有捕获和比较通道,只有计时功能
  • 4个通用定时器。分别是TIM2TIM3TIM4TIM5。可以16位向上或向下计数。可以定时、输出比较、输入捕获。每个定时器有4个外部IO。
  • 2个高级定时器。分别是TIM1TIM8。可以16位向上或向下计数。可以定时、输出比较、输入捕获、还可以输出三相电机互补信号。每个定时器有8个外部IO。

另外定时器的通道可以用来作为:

  • 输入捕获
  • 输出比较
  • PWM 生成(边缘或中间对齐模式)
  • 单脉冲模式输出

如下事件发生时产生中断/DMA:

  • 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
  • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
  • 输入捕获
  • 输出比较

2. 生成工程

2.1. 创建工程选择主控

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟

2.2. 系统配置

配置时钟源

CUBEMX怎么使用外部时钟_单片机_02


配置debug模式(如果需要ST-Link下载及调试可以勾选)

CUBEMX怎么使用外部时钟_arm_03


配置时钟树(可以直接在HCLK那里输入72,然后敲回车会自动配置)

CUBEMX怎么使用外部时钟_单片机_04

2.3. 配置工程目录

CUBEMX怎么使用外部时钟_arm_05


CUBEMX怎么使用外部时钟_上升沿_06

3. 定时器配置(三选一)

案例:

  • 定时功能:定时翻转 LED 灯
  • PWM功能:驱动电机 / 呼吸灯
  • 输入捕获:PWM信号频率计算 / 信号高电平或低电平持续时间计算

3.1. 定时功能

第一步:配置外设

LED:用来看效果

CUBEMX怎么使用外部时钟_上升沿_07


TIMER2:用来计时

CUBEMX怎么使用外部时钟_上升沿_08


在时钟树中可以看到,定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。我们这里设置预分频系数为7200-1,也就是分频完之后的频率为72 000 000 / 7200 = 10 000HZ;计数值为10000 (设置9999,还有0,就是1万)。也就是每1s产生一次溢出中断中断配置中断

CUBEMX怎么使用外部时钟_arm_09


第二步:生成代码

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟_10


第三步:写代码

打开工程,编译。

stm32f1xx_it.c中可以看到定时器中断处理函数TIM2_IRQHandler.

CUBEMX怎么使用外部时钟_stm32_11


HAL_TIM_IRQHandler(&htim2);中,根据发生事件不同,调用了不同函数,其中当发生更新事件的时候,调用了函数HAL_TIM_PeriodElapsedCallback(htim);

CUBEMX怎么使用外部时钟_stm32_12


这个函数定义如下。可以看到这是个弱函数,在注释中也有写。我们重新定义此函数,然后当中断发生时会执行此函数。此函数被称为回调函数。

CUBEMX怎么使用外部时钟_arm_13

当需要回调时,不应修改此函数,
HAL_TIM_PeriodElapsedCallback可以在用户文件中实现

我们把这个函数的重定义写在 tim.c 中。添加到末尾。

/* USER CODE BEGIN 1 */

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	// 这个回调函数 是共用的,因此需要先判断,是不是定时器2
    if(htim->Instance == htim2.Instance)
	{
		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
	}
}

/* USER CODE END 1 */

然后在main.c中,只需要在while(1)之前 使能定时器中断即可。

// 使能定时器中断
  HAL_TIM_Base_Start_IT(&htim2);

第四步:编译、下载、验证

PA8 连接的 LED 每隔一秒翻转一次,

3.2. PWM 输出

第一步:配置定时器,用于输出PWM信号

CUBEMX怎么使用外部时钟_arm_14


在时钟树中可以看到,定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。我们这里设置预分频系数为72-1,也就是分频完之后的频率为72 000 000 / 72 = 1 000 000HZ;计数值为1000 (设置999,还有0,就是1 000)。也就是每1ms产生一次溢出中断中断。PWM 生成通道 mode 参数:

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟_15

TIMx_CNT,表示定时器 TIMx 的计数器寄存器值;
TIMx_CCRn,表示捕获比较寄存器的值,其中 n 表示某个通道,取值为1、2、3、4;

PWM 生成通道 pulse 参数:决定PWM的占空比。
占空比Pulse = pulse / period

这个是可以在程序中改的
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, campNum);

第二步:生成代码

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟_10


第三步:main.c

/* USER CODE BEGIN WHILE */
  
	// 开启 PWM 输出
  	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
	
	uint16_t pwm_i = 0;			//  PWM output
	uint16_t pwm_mode = 0;		//  PWM chance mode
  while (1)
  {
		if(pwm_mode)
		{
			__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_i--);
			__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, pwm_i--);
			if(pwm_i==0)
				pwm_mode=0;
		}
		else
		{
			__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_i++);
			__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, pwm_i++);
			if(pwm_i==1000)
				pwm_mode=1;
		}
		HAL_Delay(1);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

第四步:效果验证
编译、烧录
在PA1和GND 或者 PA2和GND之间接个 LED,可以看到LED有呼吸灯的效果。

3.3. 输入捕获

第一步:定时器配置

这里使用定时器3捕获输入

CUBEMX怎么使用外部时钟_单片机_17


在时钟树中可以看到,定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。我们这里设置预分频系数为72-1,也就是分频完之后的频率为72 000 000 / 72 = 1 000 000HZ;计数值为65536(设置65535,还有0,就是65536)。因为这里其实还是需要计时,能长就长,减少更新中断。同时设置上升沿触发,也就是上升沿到来产生捕获中断。

设置捕获上升沿还是下降沿 也是可以配置的
__HAL_TIM_SET_CAPTUREPOLARITY();

使能中断

CUBEMX怎么使用外部时钟_单片机_18


使用定时器2 产生固定占空比的PWM波(配置方法和上一小节一样)

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟_19


为了方便看效果,也配置一下串口重定向,看往期文章。–>串口重定向配置<–第二步:生成代码

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟_10


第三步:写代码

tim.c末尾加上下面函数。前者为输入捕获中断回调函数。

后者用来计算 输入信号的占空比和频率。

/* USER CODE BEGIN 1 */
#include "usart.h"

uint16_t CCR1, CCR2, CCR3;
uint8_t measure_flag = 0;
// 定时器3 捕获中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	static uint8_t measure_cnt = 1;
	// 初始设置的是捕获上升沿
	if (htim == &htim3)
	{
		// 1. 第一次发生中断肯定是上升沿
		if (measure_cnt == 1)
		{
			// 2. 获取此时定时器计时数据
			CCR1 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
			// 3. 将定时器设置为捕获下降沿
			__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
			measure_cnt = 2;
		}
		// 4. 捕获到下降延
		else if (measure_cnt == 2)
		{
			// 5. 获取此时定时器计时数据
			CCR2 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
			// 6. 将定时器重新设置为捕获上升沿
			__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
			measure_cnt = 3;
		}
		// 7. 再次捕获到上升沿,说明一个周期结束了。
		else if (measure_cnt == 3)
		{
			// 8. 获取此时定时器计时的数据
			CCR3 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
			// 9. 关闭定时器中断。
			HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_1);
			measure_cnt = 1;
			measure_flag = 1;
		}
	}
}

// 捕获函数
void capture(void)
{
	// diff1:高电平持续时间
	// diff2:一个周期的时间
	uint16_t diff1 = 0, diff2 = 0;
	
	uint32_t freq;	// 频率
	uint8_t duty;	// 占空比
	if (measure_flag)
	{
		measure_flag = 0;

		if (CCR1 < CCR2)
			diff1 = CCR2 - CCR1;
		else
			diff1 = 0xffff + 1 + CCR2 - CCR1;	// 设置的最多能数65535,也就是0xffff + 1

		if (CCR1 < CCR3)
			diff2 = CCR3 - CCR1;
		else
			diff2 = 0xffff + 1 + CCR3 - CCR1;
		// 每秒能数 1000000.一个周期是 diff2。
		freq = (72000000 / 72) / diff2;
		// 高电平持续时间/低电平持续时间 不让出现小数,所以*100
		duty = diff1 * 100 / diff2;
	}
	printf("freq: %d HZ,  duty: %d %% \r\n", freq, duty);
}
/* USER CODE END 1 */

main.c中调用函数

// 开启 PWM 输出
  	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
	// 开启定时器输入捕获中断
	HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
  while (1)
  {
		capture();
		// 延时1s
		// 在中断回调函数中关掉了,再次开启定时器3捕获中断 重新计算。
		HAL_Delay(1000);
		HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

第四步:效果验证

编译、烧录

用串口查看:

用线把 PA6和PA0 或者 PA6和PA1 连接起来,就可以查看定时器2的两个通道产生的PWM频率和占空比了(下面有几个数不对,因为手动插上的时候有抖动)。

CUBEMX怎么使用外部时钟_CUBEMX怎么使用外部时钟_21