就是除了定时功能,没有其他什么功能。
从图中可以看出基本定时器就这么些内容
1、时钟源(CLK)
2、预分频寄存器(PSC)
3、自动重装载寄存器(ARR)
4、计数器寄存器(CNT)
需要理解几个概念,什么是时钟源?什么是分频器?什么是自动重载?什么是计数器?什么是溢出?
举个栗子:
拿一个量杯,量杯的容量是65535mL;
往量杯里面滴水,每秒滴1mL;
65535秒后,杯子就装满了。
可以这么理解:
这个水滴就是时钟源,每滴就是1秒钟。
这个量杯就可以看作是一个计数器了,看量杯上的刻度就知道经过多长时间。
量杯满了,也叫溢出了。
量杯满了,就自动把水倒掉,重新接水计时,这个过程叫自动重载。
现在觉得1秒钟滴1mL太快了,改为10秒钟滴1mL,这个过程叫分频,10分频;如果改为5秒钟滴1mL就是5分频,改变时间间隔的就是分频器。
量杯的容量65536mL是实验室里面最大的量杯了,觉得太大了,可以换个50000mL的量杯,40000ml的杯子,25000mL的杯子。。。。。。这个容量可以理解为自动重装载寄存器的值。
量杯滴满了,除了把水倒掉重新接水外,还可以提醒你去干点啥吧,这个干点啥就可以理解为溢出产生的事件了,也可以理解为中断。
F103的基本定时器有2个,TIM6和TIM7
TIM6和TIM7的特点:只能向上计数
什么叫向上计数:0、 1、 2 、3 。。。。。。99、100,从小往大的数叫向上计数;
什么叫向下计数:100、 99、 98 。。。。。。1、 0,从大往小的数叫向下计数;
这一个大表格看着就吓人,其实基本定时器就只有8个寄存器,
人心都是浮躁的,在哪个时代都是一样的,但是要想学点东西还是要静下心来,单片机要想学好,底层的寄存器一定要理解,别看STM32CubeMX一键生成代码很爽,在复杂的工程里面出点BUG你就GG了。
还是来看看那7个寄存器吧
APRE:Auto -reload Preload Enable 自动重装载预装载使能
0:自动重装载寄存器没有缓冲
1:自动重装载寄存器有缓冲
OPM:One-pulse mode 单脉冲模式
0:在发生更新事件时,计数器不停止
1:在发生下次更新事件时,计算器停止计数(清除CEN位)
URS:Update request source 更新请求源
该位由软件设置和清除,以选择UEV事件的请求源
0:所有状况都可以产生更新中断
1:只有计数器上溢或者下溢才可以产生更新中断或DMA
UDIS:Updata disable 禁止更新
0:更新事件使能
1:不产生更新事件
CEN :Counter Enable计数器使能
0:关闭计数器
1:使能计数器
UDE:Update DMA Request Enabel 更新DMA请求使能
0:禁止更新DMA请求
1:使能更新DMA请求
UIE:Updata interrupt enable 更新中断使能
0:禁止更新中断
1:使能更新中断
UIF:Update interrupt 更新中断标志
硬件在更新中断时设置该位,由软件清除
0:没有产生更新
1:产生了更新
UG:Update Generation 产生更新事件
0:无作用
1:重新初始化定时器的计数器,并产生对寄存器的更新
查询使用
1、参数初始化
2、打开定时器计数
3、关闭定时器
打开定时器对应的函数
//在HAL库中,使用HAL_TIM_Base_Start()函数来打开定时器
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
//
//其实HAL_TIM_Base_Start()函数就是调用__HAL_TIM_ENABLE(__HANDLE__) 函数;
//而__HAL_TIM_ENABLE(__HANDLE__) 函数就是将CR1寄存器的CEN位设置为1
__HAL_TIM_ENABLE(__HANDLE__)
关闭定时器对应的函数
//在HAL库中,使用HAL_TIM_Base_Stop()函数来关闭定时器
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim)
//
//其实HAL_TIM_Base_Stop()函数就是调用__HAL_TIM_DISABLE(__HANDLE__)函数;
//而__HAL_TIM_DISABLE(__HANDLE__)函数就是将CR1寄存器的CEN位设置为0
__HAL_TIM_DISABLE(__HANDLE__);
设置与读取计数器寄存器的初值,
默认复位之后,计数器寄存器的初值就是0,也可以自己设置个初值
//设置计数器寄存器的初值
__HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__)
//读取计数器寄存器的值
__HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNT)
设置自动重装载寄存器的值
定时器就是从计数器的初值,计到自动重装载的值之后,又从新计数;
一般计数器的初值是0,那这个自动重装的值就是周期,就是使用定时器经常看到的那个Preriod;
//__HAL_TIM_SET_AUTORELOAD()函数就是设置ARR寄存器
#define __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) \
do{ \
(__HANDLE__)->Instance->ARR = (__AUTORELOAD__); \
(__HANDLE__)->Init.Period = (__AUTORELOAD__); \
} while(0)
基本定时器只能使用系统内部时钟,看自己设置的是多少,一般设置为72MHz,72分频之后就是1MHz,一个时钟周期就是1us
使用STM3CubeMX自动生成了一些配置代码
tim源文件代码如下
#include "tim.h"
TIM_HandleTypeDef htim6;
void MX_TIM6_Init(void)
{
htim6.Instance = TIM6; //使用基本定时器TIM6
htim6.Init.Prescaler = 72-1; //72分频,72MHz 72分频之后就是1MHz
htim6.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
htim6.Init.Period = 0; //自动重装载的值先不设定,在延时函数里面设置
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //自动预装载不使能,自动装载值马上更新
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
__HAL_RCC_TIM6_CLK_ENABLE();
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
__HAL_RCC_TIM6_CLK_DISABLE();
}
}
/* USER CODE BEGIN 1 */
//
/* 函数名 Delay_us
* us级别的延时,t表示t us;
* 前提是将定时器的时钟分频到1MHz,系统时钟72MHz的话,72分频即可
*/
void Delay_us(uint16_t t)
{
uint16_t count = 0;
__HAL_TIM_SET_AUTORELOAD(&htim6, t); //写入自动重装载寄存器的值,就是计数周期
__HAL_TIM_SET_COUNTER(&htim6, 0); //将计数器初值设置为0
__HAL_TIM_ENABLE(&htim6); //打开定时器开始计数
while(count != t) //没有计到自动装载的值,就一直等着
{
count = __HAL_TIM_GET_COUNTER(&htim6); //读取计数器里面的值,赋值给count
}
__HAL_TIM_MOE_DISABLE(&htim6); //关闭定时器
}
/*
*
*ms级的延时就是1000个us级的延时
*
**/
void Delay_ms(uint16_t t)
{
uint16_t cnt;
for(cnt=0; cnt
main源文件
#include "main.h"
#include "tim.h"
#include "gpio.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM6_Init();
while (1)
{
//让LED间隔1s闪缩
HAL_GPIO_TogglePin(TEST_LED_GPIO_Port, TEST_LED_Pin);
Delay_ms(1000);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
中断使用
1、参数初始化
1、设置定时器的预分频系数
2、设置计数模式
3、设置自动重装载寄存器的值
4、预装载是否使能
2、使能定时器及其溢出中断
3、编写中断服务函数
1、检查更新中断标志位
2、检查对应的中断是不是使能了
3、清空中断标志位
4、做想做的事情,该做的事情
定时器tim.c源文件
#include "tim.h"
TIM_HandleTypeDef htim6;
/* TIM6 init function */
void MX_TIM6_Init(void)
{
htim6.Instance = TIM6; //使用定时器6
htim6.Init.Prescaler = 72-1; //72分频
htim6.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
htim6.Init.Period = 1000-1; //自动重装载值设置为1000
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //禁止预装载
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_Base_Start_IT(&htim6); //开启定时器以及中断
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
__HAL_RCC_TIM6_CLK_ENABLE();
/* TIM6 interrupt Init */
HAL_NVIC_SetPriority(TIM6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
__HAL_RCC_TIM6_CLK_DISABLE();
/* TIM6 interrupt Deinit */
HAL_NVIC_DisableIRQ(TIM6_IRQn);
}
}
在中断源文件中编写回调函数
void TIM6_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
uint16_t count = 0;
//设置的自动重装载值为1000,相当于1000us,也就是1ms中断一次,要1s后执行中断任务,就需要等1000次中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
count++;
if(count == 1000)
{
count = 0;
HAL_GPIO_TogglePin(TEST_LED_GPIO_Port,TEST_LED_Pin);
HAL_TIM_Base_Start_IT(&htim6); //在中断回调函数中再次使能中断
}
}
HAL库里面的很多函数实在是很冗长,用起来是很方便,但是最好自己还是多研究一下底层的寄存器。
用寄存器操作结合HAL库,效率会高很多,代码看起来也会精简很多,后面有机会用简化的HAL库函数来实现定时器的中断。。。。。。
STM32的定时器功能非常强大,也很复杂,看使用手册就知道,100多页,比任何其他的外设都多,在实际的应用中,定时器也是用的非常频繁,尤其在电机控制方面,所以基本定时器的应用只是个开始。