文章目录
- 1. 所用硬件
- 2. 生成工程
- 2.1. 创建工程选择主控
- 2.2. 系统配置
- 2.3. 配置工程目录
- 3. 定时器配置(三选一)
- 3.1. 定时功能
- 3.2. PWM 输出
- 3.3. 输入捕获
1. 所用硬件
正点原子Mini板,主控 STM32F103RCT6.
定时器简介
这里主要讨论通用定时器(系统嘀嗒定时器、看门狗定时器、RTC定时器不考虑在内)
对于STM32F103RCT6 单片机:
- 2个基本定时器。分别是
TIM6
、TIM7
。只能16位向上计数、没有IO口,没有捕获和比较通道,只有计时功能。 - 4个通用定时器。分别是
TIM2
、TIM3
、TIM4
、TIM5
。可以16位向上或向下计数。可以定时、输出比较、输入捕获。每个定时器有4个外部IO。 - 2个高级定时器。分别是
TIM1
、TIM8
。可以16位向上或向下计数。可以定时、输出比较、输入捕获、还可以输出三相电机互补信号。每个定时器有8个外部IO。
另外定时器的通道可以用来作为:
- 输入捕获
- 输出比较
- PWM 生成(边缘或中间对齐模式)
- 单脉冲模式输出
如下事件发生时产生中断/DMA:
- 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
2. 生成工程
2.1. 创建工程选择主控
2.2. 系统配置
配置时钟源
配置debug模式(如果需要ST-Link下载及调试可以勾选)
配置时钟树(可以直接在HCLK那里输入72,然后敲回车会自动配置)
2.3. 配置工程目录
3. 定时器配置(三选一)
案例:
- 定时功能:定时翻转 LED 灯
- PWM功能:驱动电机 / 呼吸灯
- 输入捕获:PWM信号频率计算 / 信号高电平或低电平持续时间计算
3.1. 定时功能
第一步:配置外设
LED:用来看效果
TIMER2:用来计时
在时钟树中可以看到,定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。我们这里设置预分频系数为7200-1
,也就是分频完之后的频率为72 000 000 / 7200 = 10 000HZ
;计数值为10000
(设置9999,还有0,就是1万)。也就是每1s产生一次溢出中断中断
。配置中断
第二步:生成代码
第三步:写代码
打开工程,编译。
在stm32f1xx_it.c
中可以看到定时器中断处理函数TIM2_IRQHandler
.
在HAL_TIM_IRQHandler(&htim2);
中,根据发生事件不同,调用了不同函数,其中当发生更新事件的时候,调用了函数HAL_TIM_PeriodElapsedCallback(htim);
这个函数定义如下。可以看到这是个弱函数,在注释中也有写。我们重新定义此函数,然后当中断发生时会执行此函数。此函数被称为回调函数。
当需要回调时,不应修改此函数,
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信号
在时钟树中可以看到,定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。我们这里设置预分频系数为72-1
,也就是分频完之后的频率为72 000 000 / 72 = 1 000 000HZ
;计数值为1000
(设置999,还有0,就是1 000)。也就是每1ms产生一次溢出中断中断
。PWM 生成通道 mode 参数:
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);
第二步:生成代码
第三步: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捕获输入
在时钟树中可以看到,定时器都是挂在APB1和APB2上,时钟频率都一样,都是72MHZ。我们这里设置预分频系数为72-1
,也就是分频完之后的频率为72 000 000 / 72 = 1 000 000HZ
;计数值为65536
(设置65535,还有0,就是65536)。因为这里其实还是需要计时,能长就长,减少更新中断。同时设置上升沿触发,也就是上升沿到来产生捕获中断。
设置捕获上升沿还是下降沿 也是可以配置的
__HAL_TIM_SET_CAPTUREPOLARITY();
使能中断
使用定时器2 产生固定占空比的PWM波(配置方法和上一小节一样)
为了方便看效果,也配置一下串口重定向,看往期文章。–>串口重定向配置<–第二步:生成代码
第三步:写代码
在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频率和占空比了(下面有几个数不对,因为手动插上的时候有抖动)。