赞
踩
工作在定时模式时:预分频时钟CK_PSC
= 定时器时钟TIM_CLK
(来自APB总线)
预分频模块
CK_PSC
进行分频TIMx_PSC
:设置预分频系数PSC如:CK_PSC = TIM_CLK = PCLK2 = 72M
,设置预分频系数PSC = 3
时,则计数时钟CK_CNT = 72 / (3 + 1) = 18M
.
因此是从0计数到PSC的,因此实际计数值为PSC+1。
计数模块
TIMx_CNT
:存放核心计数器运行时的当前计数值自动重载模块
由自动重载寄存器TIMx_ARR
组成,根据定时器不同的计数模式:
递增计数模式:TIMx_ARR
值为核心计数器的计数终值
递减计数模式:TIMx_ARR
值为核心计数器的计数初值
TIMx_ARR
),然后产生上溢事件(可触发更新中断),同时再从0开始继续计数TIMx_ARR
)计数到0,然后重新从自动重载值ARR
开始计数并生成计数器下溢事件TIMx_ARR - 1
,产生计数器上溢事件;然后从TIMx_ARR
开始向下计数到1
并生成计数器下溢事件。之后再从0开始重新计数。计数值溢出值与计数器重载值的关系表:
计数模式 | 计数器溢出值 | 计数器重载值 |
---|---|---|
递增计数 | CNT = ARR | CNT = 0 |
递减计数 | CNT = 0 | CNT = ARR |
中心对齐计数 | CNT = ARR - 1 | CNT = ARR |
中心对齐计数 | CNT =1 | CNT = 0 |
一般使用递增计数。
计数频率CK_CNT = CK_PSC / 2
,当计数器使能时(TIMx_CR1
寄存器CEN
置1),每个CK_CNT
的上升沿计数器寄存器CNT
值+1,当从0加到ARR时,触发计数器上溢事件,同时将更新事件标志UEV置位,触发定时器更新中断。
因此,可得到定时周期计算公式:
定时时间 = 计数值 / 时钟频率CK_CNT
其中,计数值 = ARR + 1
, 时钟频率 = CK_CNT = CK_PSC / PSC + 1
,最后得到:
如:定时器时钟频率为72M,ARR = 999, PSC = 71,则定时器周期 = 1000 * 72 / 72M = 1ms
计数频率受限于PSC,定时周期(频率)受限于PSC与ARR。例如,72M计数频率1秒钟理论上最多有72000000个计数,但是计数值CNT和自动重载值ARR的范围为0~65535,即最大计数65535,定时频率最大为1098.65HZ,因此将PSC设为定值,根据ARR的范围可以得到在相应的PSC值下的定时频率范围(F103的几组典型值):
计数频率CK_CNT/HZ | PSC | 最高定时频率/HZ(ARR=1) | 最低定时频率/HZ(ARR=65535) |
---|---|---|---|
72M | 1-1 | 36M | 1098.65 |
36M | 2-1 | 18M | 549.32 |
1M | 72-1 | 500K | 15.26 |
100K | 720-1 | 50K | 1.526 |
65454 | 1100-1 | 32727 | 1 |
60K | 1200-1 | 30K | 0.9155 |
注意:ARR=0
定时器不工作
上述讨论的是
16
位定时器,对于32
位定时器,ARR最大值为4294967295
每个定时器具有1~4个独立的通道,各通道具有独立的输入捕获单元、捕获/比较寄存器、输出比较单元,且共享一个时基单元。因此,每个通道可以选择输出比较和输入捕获功能,但共用一个捕获/比较寄存器,只能2选1。
用于捕获外部触发信号,捕获方式为上升沿/下降沿/双边沿捕获。发生捕获事件时,将此刻计数器的值锁存到比较/捕获寄存器中,同时可以产生捕获中断。
TIMx_CCR寄存器在输入捕获模式下用于存放发送捕获事件时的当前计数值;在输出比较模式下用于存放预设的比较值。该寄存器具备预装载功能。
用于信号输出。定时器通过将预设的比较值与计数器的值做匹配比较,以实现各类输出,如PWM输出、单脉冲输出等。
PWM输出基于输出比较模式,将计数值CNT与捕获/比较值CCR就行比较,根据比较结果和工作模式决定有效电平,同时可设置有效电平为低电平还是高电平。
PWM的工作模式
通过TIMx_CCMRx
寄存器OCxM
位配置:
有效电平配置
通过TIMx_CCER
寄存器CCxP
位配置(CCxP
中的x为通道号):
CCxP = 0:有效电平为高电平
CCxP = 1:有效电平为低电平
下图为PWM模式2(向上计数)、有效电平为高电平(或者可以看作PWM模式1<向上计数>、有效电平为低电平):
此PWM配置下的频率与占空比计算公式:
PWM频率:TIMx_CLK / ((ARR + 1) * (PSC+ 1))
PWM占空比:(1 - CCR / (ARR + 1)) * 100%
一般为了占空比计算方便,使用PWM模式1(向上计数)、有效电平为高电平,即CNT < CRR
时输出高电平
PWM占空比:CCR / (ARR + 1) * 100%
PWM模式1向上计数CNT < CCR为有效电平,CNT最大值为ARR,因此CCR要为ARR+1,才能保证占空比100%
注意:**高级定时器(Advanced-control timers)**要想输出PWM波形,必须将TIMx_BDTR
寄存器的MOE
位置位,以使能主输出,否则不会输出PWM。
定时器的每个通道都可以输出PWM信号,但因为共享同一个自动重装载寄存器ARR,因此可以输出占空比不同,但周期相同的PWM信号。(在输出比较模式下可以软件切换完成不同占空比和不同周期的PWM输出)
CNT与CCR比较,根据比较结果输出有效/无效电平(OC1REF
=0 无效电平, OC1REF
=1 无效电平<注意这不是寄存器,只是个电平信号>)
TIMx_CCMR1
寄存器的OCxM[2:0]
位:用于设置PWM模式
TIMx_CCER
寄存器的CCxP
位:捕获 / 比较输出极性,下面为通道配置为输出的情况:
TIMx_CCER
寄存器的CCxE
位:捕获 / 比较输出使能
0:关闭使能
1:打开使能
OC:最终输出的电平信号
输入捕获一般用于测量信号的周期、频率、占空比等。
CCMRx
寄存器的CCxS
位):
输入捕获过程示意图:
捕获差值diff(CCR2与CCR3同理) :
diff = CCR2 - CCR1
diff = ARR + 1 - (CCR1 - CCR2)= ARR + 1 - CCR1 + CCR2
若计数器溢出产生更新中断,此时需要在更新中断函数中记录溢出次数,每溢出一次diff加上ARR,说明信号周期太长了(一般计数值CNT都设为最大)。注意出现更新中断不代表信号周期太长导致的溢出:
如上图,第一次捕获后,计数值计数到最大值65535(16位定时器)发生更新中断,但是第二次捕获到的CCR值小于第一次捕获到的值,此时diff并没有大于65535,因此记录溢出次数时还需要判断两次捕获到的CCR值的大小关系。
令CCR1与CCR2捕获差值为diff1,CCR1与CCR3捕获差值为diff2
信号频率/周期与占空比计算
TIM_CLK / (PSC + 1) / diff2 = TIM_CLK / (diff2 * (PSC + 1))
diff2 / TIM_CLK * (PSC + 1)
diff1 / diff2 * 100%
PWM输入捕获是输入捕获模式的一个特例,仅存在以下不同:
如CH1为输入通道,IC1选择直接输入,IC2选择间接输入:
此时第一次捕获时会复位计数器,即将CNT置零,IC2捕获脉宽,IC1捕获周期
当CH1为输入通道,IC1选择间接输入,IC2选择直接输入时,IC1捕获脉宽,IC2捕获周期。
注意:仅定时器的通道1和2可以进行pwm输入捕获。
typedef struct
{
uint32_t Prescaler; // 预分频系数PSC
uint32_t CounterMode; // 计数模式
uint32_t Period; // 自动重装载值ARR (不能为0)
uint32_t ClockDivision; // 设置定时器时钟TIM_CLK的分配值,用于输入信号滤波
uint32_t AutoReloadPreload; // 自动重载预加载值, 表示设置ARR寄存器内容的生效时刻
} TIM_Base_InitTypeDef;
CounterMode
#define TIM_COUNTERMODE_UP // 递增计数模式
#define TIM_COUNTERMODE_DOWN // 递减计数模式
#define TIM_COUNTERMODE_CENTERALIGNED1 // 中心对齐计数模式1
#define TIM_COUNTERMODE_CENTERALIGNED2 // 中心对齐计数模式2
#define TIM_COUNTERMODE_CENTERALIGNED3 // 中心对齐计数模式3
TIMx_CR1
寄存器:
三种中心对齐计数模式的区别主要为输出比较中断标志位的设置方式。
一般选择递增计数模式。
ClockDivision
#define TIM_CLOCKDIVISION_DIV1 // 对定时器时钟TIM_CLK进行1分频
#define TIM_CLOCKDIVISION_DIV2 // 对定时器时钟TIM_CLK进行2分频
#define TIM_CLOCKDIVISION_DIV4 // 对定时器时钟TIM_CLK进行4分频
TIMx_CR1
寄存器:
主要用于输入信号的滤波,一般使用默认值:1分频。
AutoReloadPreload
#define TIM_AUTORELOAD_PRELOAD_DISABLE // 预装载功能关闭
#define TIM_AUTORELOAD_PRELOAD_ENABLE // 预装载功能开启
TIMx_CR1
寄存器:
主要功能:
TIMx_ARR
的预装载功能,即自动重装寄存器的内容是更新事件产生时写入有效,还是立即写入有效最后调用HAL_TIM_Base_Init
函数完成定时器的时钟、中断、引脚的配置
HAL_TIM_Base_Start(TIM_HandleTypeDef *htim); // 轮询模式启动定时器
HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim); // 轮询模式关闭定时器
HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim); // 中断模式启动定时器
__HAL_TIM_CLEAR_IT
来清除更新中断标志HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim); // 中断模式关闭定时器
HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim); // DMA模式启动定时器
HAL_TIM_Base_Stop_DMA(TIM_HandleTypeDef *htim); // DMA模式关闭定时器
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成中断处理:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
完成定时器更新中断的处理。
#define __HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNT)
/** @brief Clear the specified TIM interrupt flag. * @param __HANDLE__ specifies the TIM Handle. * @param __FLAG__ specifies the TIM interrupt flag to clear. * This parameter can be one of the following values: * @arg TIM_FLAG_UPDATE: 更新中断标志 * @arg TIM_FLAG_CC1: 通道1的捕获/比较中断标志 * @arg TIM_FLAG_CC2: 通道2的捕获/比较中断标志 * @arg TIM_FLAG_CC3: 通道3的捕获/比较中断标志 * @arg TIM_FLAG_CC4: 通道4的捕获/比较中断标志 * @arg TIM_FLAG_TRIGGER: Trigger interrupt flag * @arg TIM_FLAG_CC1OF: Capture/Compare 1 overcapture flag * @arg TIM_FLAG_CC2OF: Capture/Compare 2 overcapture flag * @arg TIM_FLAG_CC3OF: Capture/Compare 3 overcapture flag * @arg TIM_FLAG_CC4OF: Capture/Compare 4 overcapture flag * @retval The new state of __FLAG__ (TRUE or FALSE). */ #define __HAL_TIM_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->SR = ~(__FLAG__))
MCU使用的是STM32L071,主频32M,配置1ms周期的定时器
PSC = 32 - 1
向上(递增)计数模式
ARR = 1000 - 1
不使能自动重载预装载功能
Trigger Output(TRGO)
:用于定时器同步或链接。当一个定时器处于主模式时,它可以对另一个处于从模式的定时器的计数器进行复位、启动、停止或提供时钟等操作。(不使用主从模式,默认即可)
NVIC使能定时器更新中断:
int main(void)
{
[...]
MX_TIM6_Init();
// 清除定时器初始化过程中的更新中断标志,避免定时器一启动就进入中断
__HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
// 使能定时器6更新中断并启动定时器
HAL_TIM_Base_Start_IT(&htim6);
[...]
}
uint16_t led_cnt = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim6)
{
if (++led_cnt % 1000 == 0)
BOARD_LED_TOGGLE;
}
}
定时中断流程:
将时钟源由内部时钟改为外部时钟模式2(外部触发引脚ETR提供)
TIM2_ETR
引脚(PA0)发送一个2ms周期的脉冲 if (key_flag)
{
key_flag = 0;
HAL_GPIO_WritePin(GPIOA, GPIO_pin_0, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(GPIOA, GPIO_pin_0, GPIO_PIN_RESET);
HAL_Delay(1);
printf("cnt: %u.\n", __HAL_TIM_GET_COUNTER(&htim2));
}
实验结果:每次按键按下,TIM2->CNT++
typedef struct
{
uint32_t OCMode; // 定时器输出模式
uint32_t Pulse; // 捕获/比较值CRR,即TIMx_CRR寄存器的内容
uint32_t OCPolarity; // 设置通道输出有效电平的极性
uint32_t OCNPolarity; // 设置互补通道输出有效电平的极性(用于互补输出)
uint32_t OCFastMode; // 快速输出模式使能,该参数只在PWM模式1和2下有效
uint32_t OCIdleState; // 设置空闲状态下通道输出的电平状态(用于互补输出)
uint32_t OCNIdleState; // 设置空闲状态下互补通道输出的电平状态(用于互补输出)
} TIM_OC_InitTypeDef;
OCMode
#define TIM_OCMODE_TIMING // 输出比较冻结模式, 匹配时无通道输出
#define TIM_OCMODE_ACTIVE // 匹配时设置通道输出为有效电平
#define TIM_OCMODE_INACTIVE // 匹配时设置通道输出为无效电平
#define TIM_OCMODE_TOGGLE // 匹配时设置通道输出电平翻转
#define TIM_OCMODE_PWM1 // PWM输出模式1
#define TIM_OCMODE_PWM2 // PWM输出模式2
#define TIM_OCMODE_FORCED_ACTIVE // 不进行匹配, 强制通道输出为有效电平
#define TIM_OCMODE_FORCED_INACTIVE // 不进行匹配, 强制通道输出为无效电平
匹配:CNT = CCR
OCPolarity
#define TIM_OCPOLARITY_HIGH // 输出有效电平为高电平
#define TIM_OCPOLARITY_LOW // 输出有效电平为低电平
OCFastMode
#define TIM_OCFAST_DISABLE // 不使能快速输出模式
#define TIM_OCFAST_ENABLE // 使能快速输出模式
注意该参数只在PWM模式1和2下有效
/**
* @brief Starts the PWM signal generation.
* @param htim TIM handle
* @param Channel TIM Channels to be enabled
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @arg TIM_CHANNEL_3: TIM Channel 3 selected
* @arg TIM_CHANNEL_4: TIM Channel 4 selected
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
/** * @brief Set the TIM Capture Compare Register value on runtime without calling another time ConfigChannel function. * @param __HANDLE__ TIM handle. * @param __CHANNEL__ TIM Channels to be configured. * This parameter can be one of the following values: * @arg TIM_CHANNEL_1: TIM Channel 1 selected * @arg TIM_CHANNEL_2: TIM Channel 2 selected * @arg TIM_CHANNEL_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @param __COMPARE__ specifies the Capture Compare register new value. * @retval None */ #define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \ (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\ ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\ ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\ ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))
MCU使用STM32F407(主频168M),配置定时器3通道1输出200HZ,占空比为50%的PWM波:
Counter Settings
Trigger Output (TRGO) Parameters
PWM Generation Channel 1
则占空比为 CRR/(ARR+1) = 2500 / (4999+1) = 50%
注意:在大多数情况下,选择开启CCR寄存器的预装载功能,让占空比的变化在下一个PWM信号周期才生效,但是如果利用输出比较的翻转模式生成多通道不同周期与不同转占空比的PWM波,则需要关闭预装载功能,让CCR寄存器的修改立即生效!
在main函数中定时器初始化完成后,添加下面代码即可:
// 启动定时器3的通道1输出频率为200HZ,占空比为50%的PWM信号
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
typedef struct
{
uint32_t ICPolarity; // 设置输入信号的捕获边沿
uint32_t ICSelection; // 选择输入捕获通道
uint32_t ICPrescaler; // 设置输入信号的预分频系数,取值为1/2/4/8
uint32_t ICFilter; // 设置输入信号的滤波器长度, 取值范围0~0xf
} TIM_IC_InitTypeDef;
ICPolarity
#define TIM_ICPOLARITY_RISING // 上升沿捕获
#define TIM_ICPOLARITY_FALLING // 下升沿捕获
#define TIM_ICPOLARITY_BOTHEDGE // 双边沿捕获
ICSelection
#define TIM_ICSELECTION_DIRECTTI // 直接输入模式(捕获通道CH1和CH2分别与IC1和IC2相连, 捕获通道CH3和CH4分别与IC3和IC4相连)
#define TIM_ICSELECTION_INDIRECTTI // 间接输入模式(捕获通道CH1和CH2分别与IC2和IC1相连, 捕获通道CH3和CH4分别与IC4和IC3相连)
#define TIM_ICSELECTION_TRC // 选择从模式管理器的触发信号TRC作为捕获信号
ICPrescaler
#define TIM_ETRPRESCALER_DIV1 // 检测到输入信号的每1个有效边沿触发1次捕获
#define TIM_ETRPRESCALER_DIV2 // 检测到输入信号的每2个有效边沿触发1次捕获
#define TIM_ETRPRESCALER_DIV4 // 检测到输入信号的每4个有效边沿触发1次捕获
#define TIM_ETRPRESCALER_DIV8 // 检测到输入信号的每8个有效边沿触发1次捕获
一般用中断方式启动:
HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_TIM_IC_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
该函数需要由用户调用,用于禁止捕获中断,关闭输入捕获通道,停止定时器运行
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
HAL_TIM_IRQHandler
调用,完成所有定时器输入捕获中断的任务处理uint32_t HAL_TIM_ReadCapturedValue(TIM_HandleTypeDef *htim, uint32_t Channel);
返回:htim->Instance->CCRx
/** * @brief Set the TIM Capture x input polarity on runtime. * @param __HANDLE__ TIM handle. * @param __CHANNEL__ TIM Channels to be configured. * This parameter can be one of the following values: * @arg TIM_CHANNEL_1: TIM Channel 1 selected * @arg TIM_CHANNEL_2: TIM Channel 2 selected * @arg TIM_CHANNEL_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @param __POLARITY__ Polarity for TIx source * @arg TIM_INPUTCHANNELPOLARITY_RISING: 上升沿 * @arg TIM_INPUTCHANNELPOLARITY_FALLING: 下降沿 * @arg TIM_INPUTCHANNELPOLARITY_BOTHEDGE: 双边沿 * @retval None */ #define __HAL_TIM_SET_CAPTUREPOLARITY(__HANDLE__, __CHANNEL__, __POLARITY__) \ do{ \ TIM_RESET_CAPTUREPOLARITY((__HANDLE__), (__CHANNEL__)); \ TIM_SET_CAPTUREPOLARITY((__HANDLE__), (__CHANNEL__), (__POLARITY__)); \ }while(0)
MCU使用为STM32F103,主频72M,配置定时器2的CH1为捕获通道:
定时器模式配置
Counter Settings
Input Capture Channel 1
最后使能定时器中断。
配置定时器3的通道1输出PWM波:
PWM频率为10K,占空比为25%
启动定时器和PWM输出:
void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { [...] MX_TIM2_Init(); MX_TIM3_Init(); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); while (1) { capture(); } }
编写捕获中断回调函数HAL_TIM_IC_CaptureCallback
:
uint16_t CCR1, CCR2, CCR3; uint8_t measure_flag = 0; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { static uint8_t measure_cnt = 1; if (htim == &htim2) { if (measure_cnt == 1) { CCR1 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); measure_cnt = 2; } else if (measure_cnt == 2) { CCR2 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); measure_cnt = 3; } else if (measure_cnt == 3) { CCR3 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1); measure_cnt = 1; measure_flag = 1; } } }
每1秒打印一次测量结果并再次开启捕获中断:
uint32_t freq; uint8_t duty; void capture(void) { uint16_t diff1 = 0, diff2 = 0; if (measure_flag) { measure_flag = 0; if (CCR1 < CCR2) diff1 = CCR2 - CCR1; else diff1 = 0xffff + 1 + CCR2 - CCR1; if (CCR1 < CCR3) diff2 = CCR3 - CCR1; else diff2 = 0xffff + 1 + CCR3 - CCR1; freq = (72000000 / 72) / diff2; duty = diff1 * 100 / diff2; printf("freq: %dHZ, duty: %d%%\r\n", freq, duty); HAL_Delay(1000); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); } }
END
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。