赞
踩
就是除了定时功能,没有其他什么功能。
从图中可以看出基本定时器就这么些内容
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<t; cnt++)
- {
- Delay_us(1000);
- }
-
- }
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多页,比任何其他的外设都多,在实际的应用中,定时器也是用的非常频繁,尤其在电机控制方面,所以基本定时器的应用只是个开始。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。