首页 > 其他分享 >STM32&定时器(超明白超细致超核心)

STM32&定时器(超明白超细致超核心)

时间:2024-07-22 21:56:18浏览次数:9  
标签:定时器 细致 Init timer STM32 TIM init HAL

可以说,STM32的定时器是32这块芯片中功能最多,结构最复杂,内容涉及最多的外设之一了。从学习形式来看,可以归纳为两个层面:原理层和软件层。那么由于内容太多,我也按照这个架构来逐一进行讲解。

目录

一:定时器架构与原理

1.时基

2.计数方式

3.定时器的架构

二.定时器功能

1.基础溢出中断功能

2.输出比较功能

3.输入捕获 

4.时钟来源配置

三:API的总结

时基API

输出比较API

输入捕获API

外源时钟配置API


一:定时器架构与原理

1.时基

时基是STM32所有定时器的核心区域,任何类别的定时器都具有时基结构。它的原理也非常的典型并且简单:

其中:
CLK        时钟来源,一般来说时HCLK,特殊情况下还可以从输入捕获通道讲来源配置到外部。
PSC        分频频率,说白了就是对HCLK进行分频的寄存器
ARR        重装值这个表述其实并不准确,后续细讲。

2.计数方式

计数方式分为三种:
        向上计数法向下计数法:这两种计数法都非常好理解。一种是递减计数,一种是递增计数。但是这里就能看出来,如果ARR单纯的是当溢出后重装给定时器的值,那么这样是完全不合理的。所以准确的说:向下计数时,ARR是重装值,向上计数时,ARR时是溢出值。
        中心对齐计数法:先从0开始向上计数,当CNT=ARR-1时,可以产生中断。此时开始向下计数,当CNT=1时,可以再次产生中断。       

听不懂?好说,上图:

另外,这里可以得出一条公式:

Tout = (PSC+1)*(ARR+1)/Ft

其中,Ft是时钟来源频率,Tout是溢出的时间长度。

3.定时器的架构

定时器的种类有三种,在32中一般分为:
基本定时器                拥有基本定时功能和中断功能
通用定时器                拥有基本定时器功能外加输入捕获;输出比较;内外时钟源选择
高级定时器                拥有通用定时器所有功能,外加重复计数器;死区重生;互补和刹车输入

从这你就能开出来定时器的功能有多么丰富,再看看架构图:

这是一个通用定时器的架构,其实主要就分三部分:

时钟来源---时基---GPIO--通道

其中,通道就是下面这些重复的部分,也是实现主要功能的区域。每一个寄存器都配备有很多个通道,并且对应的GPIO口对应的通道往往是确定的:比如LED1

这里的TIM4_CH3就是定时器4通道3.
 

二.定时器功能

通道是32定时器的核心学习内容,可以说定时器这个部分复杂就复杂在了通道这个部分。其原因就是 STM32的所有的定时器特殊功能几乎都来源于这些通达。

1.基础溢出中断功能

当定时器的值小于0或者当它大于ARR又或者是到达某个设定特殊值时,将产生中断,这个原理非常简单,主要是软件的配置部分对于不熟悉新手而言比较饶。

我给个基本的步骤吧:
时基的基本的配置 ---> 时钟的使能 ---> GPIO配置(可以没有)--->NVIC配置 ---> 中断服务函数配置 ---> 中断溢出回调函数具体配置
其中,按颜色来分布记忆最好,开始成为时基的配置,中间为MSP配置,最后为中断内容配置。

废话少说,我上代码吧:

#include "timer.h"
#include "led.h"
	TIM_HandleTypeDef timer_init = {0};
void Timer_init(uint16_t arr,uint16_t psc){

	
	timer_init.Instance = TIM2;	//选择定时器
	timer_init.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;	//重装使能
	timer_init.Init.CounterMode = TIM_COUNTERMODE_UP;		//计数方式
	timer_init.Init.Period = arr;		//arr,重装载初始值
	timer_init.Init.Prescaler = psc;	//psc,	分频数
	
	HAL_TIM_Base_Init(&timer_init);
	HAL_TIM_Base_Start_IT(&timer_init);
}
//初始化msp,该函数会被HAL_TIM_Base_Init自动调用,就是像中断中的公共处理函数一样
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM2){
		__HAL_RCC_TIM2_CLK_ENABLE();
		HAL_NVIC_SetPriority(TIM2_IRQn,0,2);
		HAL_NVIC_EnableIRQ(TIM2_IRQn);
		
	}
}

void TIM2_IRQHandler(){
	
	HAL_TIM_IRQHandler(&timer_init);

}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *timer_init){
	if(timer_init->Instance == TIM2)
	led_triggle(); 
}

这是一个溢出中断控制小灯规律亮灭的配置。
这里我带一个常识:
HAL库中的中断函数往往是 中断服务函数---中断公共处理函数---xxx功能回调函数 其中回调函数的数量往往是相当的庞大的,你找个函数能累死你的,所以一会儿到最后我给一些常用的通用的API做个总结。

2.输出比较功能

输出比较功能是指,存在一个寄存器可以将自身的值和定时器的CNT进行比较,通过结果来控制对应的GPIO口的极性。

图中可以看到的 捕获/比较寄存器  就是CRR寄存器。说白了,就是把CRR的值跟CNT比较以下,然后可以根据结果来调整对应输出接的GPIO口的电平,就这么简单。


CNT是变化的,也就是说,单位周期里一段时间内有CNT>CRR也有CNT<CRR。并且CNT的变化速度是平均的,所以我们完全可以通过控制CRR来控制单位时间内CNT有多久>CRR多久<。对应到GPIO口就是单位时间内可以控制多久位高电平多久为低电平。

怎么样,有没有觉得很熟悉呼之欲出?是的,完全可以搞出来一个PWM波。
上代码!

//PWM部分:
#include "pwm.h"
#include "led.h"
	TIM_HandleTypeDef timer_init = {0};
void Timer_init(uint16_t arr,uint16_t psc){

	
	timer_init.Instance = TIM4;	//选择定时器
	timer_init.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;	//重装使能
	timer_init.Init.CounterMode = TIM_COUNTERMODE_UP;		//计数方式
	timer_init.Init.Period = arr;		//arr,重装载初始值
	timer_init.Init.Prescaler = psc;	//psc,	分频数
	
	TIM_OC_InitTypeDef pwm_config = {0};
	pwm_config.OCMode = TIM_OCMODE_PWM1;
	pwm_config.Pulse = arr/2;
	pwm_config.OCPolarity = TIM_OCPOLARITY_LOW;
	
	HAL_TIM_PWM_Init(&timer_init);
	HAL_TIM_PWM_ConfigChannel(&timer_init,&pwm_config,TIM_CHANNEL_3);
	HAL_TIM_PWM_Start_IT(&timer_init,TIM_CHANNEL_3);
}
//初始化msp,该函数会被HAL_TIM_Base_Init自动调用,就是像中断中的公共处理函数一样
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM4){
		__HAL_RCC_TIM4_CLK_ENABLE();
		__HAL_RCC_GPIOB_CLK_ENABLE();
		
		GPIO_InitTypeDef gpio_init={0};
		
		gpio_init.Pin = GPIO_PIN_8;
		gpio_init.Pull = GPIO_PULLUP;
		gpio_init.Mode = GPIO_MODE_AF_PP;
		gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;

		HAL_GPIO_Init(GPIOB,&gpio_init);
		
	}
}

void CRR_SET(uint16_t val){
	__HAL_TIM_SET_COMPARE(&timer_init, TIM_CHANNEL_3, val);
}


//main部分
int main(void)
{
	stm32_clock_init(RCC_PLL_MUL9);
    HAL_Init();                        
	led_init();
	Timer_init(500 - 1,72 - 1);
	
	uint16_t i = 0;
    while(1)
    { 
		for(i = 0; i<200; i++){
			CRR_SET(2*i);
			delay_ms(5);
		}
		
		for(i = 0; i<200; i++){
			CRR_SET(499 - 2*i);
			delay_ms(5);
		}
    }
}

这里最后就是用PWM波控制了一手小灯,让他慢慢的亮慢慢的灭。俗称呼吸灯。

3.输入捕获 

其实输入捕获和输出比较用的是同一个寄存器,只不过重点和使用方法变了:

是的,之前是通过CRR最后控制到GPIO上,这次反过来了,这次是通过GPIO控制到CRR上了。

那么具体的是怎么个事儿呢?
就是通过检测某一个GPIO引脚的电平,通过配置产生中断。此时内部的CRR接收到响应的请求后通过更新中断捕获此时定时器的CNT的值,并把该值赋值给了CRR。

这其实还是不难,但是说白了,这里所有的难度都在于软件配置的理解上,废话不多说,上代码:
 

#include "pwm.h"
#include "led.h"
#include "uart1.h"
#include "string.h"
struct{
	uint8_t success_flag;
	uint8_t falling_flag;
	uint8_t rasing_flag;
	uint8_t timeout_cnt;
	uint16_t count;
}FLAG = {0};

TIM_HandleTypeDef timer_init = {0};

void Timer_init(uint16_t arr,uint16_t psc){

	//时基初始化
	timer_init.Instance = TIM2;								//选择定时器
	timer_init.Init.CounterMode = TIM_COUNTERMODE_UP;		//计数方式
	timer_init.Init.Period = arr;							//arr,重装载初始值
	timer_init.Init.Prescaler = psc;						//psc,	分频数
	
	HAL_TIM_IC_Init(&timer_init);
	
	
	//通道的选择和配置
	TIM_IC_InitTypeDef chennel_config = {0};
	chennel_config.ICFilter = 0;
	chennel_config.ICPolarity = TIM_ICPOLARITY_FALLING;		//指定触发电平边沿
	chennel_config.ICPrescaler = TIM_ICPSC_DIV1;			//指定分频数
	chennel_config.ICSelection = TIM_ICSELECTION_DIRECTTI;	//指定输入
	
	HAL_TIM_IC_ConfigChannel(&timer_init,&chennel_config,TIM_CHANNEL_2);	
	__HAL_TIM_ENABLE_IT(&timer_init,TIM_IT_UPDATE);
	HAL_TIM_IC_Start_IT(&timer_init, TIM_CHANNEL_2);

}
//初始化msp,该函数会被HAL_TIM_Base_Init自动调用,就是像中断中的公共处理函数一样
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM2){
		__HAL_RCC_TIM2_CLK_ENABLE();				//定时器时钟配置
		__HAL_RCC_GPIOA_CLK_ENABLE();				//引脚时钟配置
		
		GPIO_InitTypeDef gpio_init={0};		
		gpio_init.Pin = GPIO_PIN_1;
		gpio_init.Pull = GPIO_PULLUP;
		gpio_init.Mode = GPIO_MODE_INPUT;
		gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;

		HAL_GPIO_Init(GPIOA,&gpio_init);
		        
		HAL_NVIC_SetPriority(TIM2_IRQn, 2, 2);		//NVIC中断优先组设置
        HAL_NVIC_EnableIRQ(TIM2_IRQn);				//NVIC中断使能
		
	}
}

void TIM2_IRQHandler(){
	HAL_TIM_IRQHandler(&timer_init);
}

//公共中断处理函数中的捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
	
	if(htim->Instance == TIM2){
		if(FLAG.success_flag == 0){
			if(FLAG.falling_flag == 0){
				FLAG.count = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);						//捕获当前定时器内的val
				led_on();
				FLAG.falling_flag = 1;
                TIM_RESET_CAPTUREPOLARITY(&timer_init, TIM_CHANNEL_2);							//清空通道设置
                TIM_SET_CAPTUREPOLARITY(&timer_init, TIM_CHANNEL_2, TIM_ICPOLARITY_RISING);		//设置通道设置		
				
			}else{	
				FLAG.count -= HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);					//再次捕获当前val
				FLAG.success_flag = 1;
				led_off();
				printf("按下按键时长:%d us /r/n",FLAG.count+FLAG.timeout_cnt*65536);			//计算
				TIM_RESET_CAPTUREPOLARITY(&timer_init, TIM_CHANNEL_2);							//清空通道设置
                TIM_SET_CAPTUREPOLARITY(&timer_init, TIM_CHANNEL_2, TIM_ICPOLARITY_FALLING);	//设置通道设置	
				memset(&FLAG,0,sizeof(FLAG));
			}		
		}
	
	}
	
	
}

//溢出中断函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	
	FLAG.timeout_cnt ++;	//记录溢出了多少次
	
}




到这里你就发现了,钩子配置三次三次配置连时基的初始函数都不一样。哈哈是的,非常之c蛋,所以我才说到最最后的时候我给你来个API总结。说白了也是给我自己做个总结,因为钩子实在是TM太多了。 
        这个代码的配置我就不写流程了,和我之前说的流程是吻合的。

4.时钟来源配置

如果你能撑到这里还在看,那哥们你确实是挺行的,因为作者我写到这儿我都有点受不了了。

废话不多说,这里的功能其实也是非常简单。默认的定时器本来的来源基本都是HCLK,也就是72Mhz,奥对了,我之前居然不知道这里的72M其实是Mllion就是一百万的意思。扯回来,所以这里其实就是配置一个外面的时钟源,原理上没啥好讲的。

唯一有一点要注意就是这里多了一个部分:叫从模式控制器配置

然后这里只上核心代码吧:

	TIM_SlaveConfigTypeDef Slave_config = {0};
	Slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
	Slave_config.InputTrigger = TIM_TS_TI2FP2;
	Slave_config.TriggerFilter = 0;
	Slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_FALLING;
	
	HAL_TIM_SlaveConfigSynchro(&timer_init,&Slave_config);
	HAL_TIM_IC_Start(&timer_init,TIM_CHANNEL_2);

这里选择TIM2 和 TIM_CHANNEL_2的原因是因为这里把外源配置给了Key1,也就是最后我按一下定时器的CNT++一下。 

三:API的总结

我是真不想做这个部分,但是还是觉得挺必要的:

时基API

HAL_TIM_Base_Init()
HAL_TIM_Base_Start_IT()
HAL_TIM_Base_MspInit()
HAL_TIM_PeriodElapsedCallback()
__HAL_RCC_TIM4_CLK_ENABLE()

输出比较API

HAL_TIM_PWM_Init()
HAL_TIM_PWM_ConfigChannel()
HAL_TIM_PWM_Start_IT()
HAL_TIM_PWM_MspInit()

输入捕获API

HAL_TIM_IC_Init()
HAL_TIM_IC_ConfigChannel()
__HAL_TIM_ENABLE_IT()
HAL_TIM_IC_Start_IT()
HAL_TIM_IC_MspInit()
HAL_TIM_IC_CaptureCallback()
TIM_RESET_CAPTUREPOLARITY
TIM_SET_CAPTUREPOLARITY

外源时钟配置API

HAL_TIM_SlaveConfigSynchro()

好了,作者我彻底干了,这里这些API没有一个是重复的,但是功能都是非常相似的,所以我称之为初学者收集者。来一个给你劝退一个,我的建议是如果你看原理图听各种流程各种难受,那你就直接上项目。东西整出来了什么你也就会了,别丧失自信。

标签:定时器,细致,Init,timer,STM32,TIM,init,HAL
From: https://blog.csdn.net/wwwkkkxxx12138/article/details/140612388

相关文章

  • VScode利用EIDE和cortex-debug进行stm32开发(也适用51)
    文章目录前言必要准备第一步:安装vscode插件——EIDEEIDE是什么EIDE的下载**EIDE的配置(重点)**EIDE功能的简单介绍1.项目的建立:新建项目或者导入项目2.为项目选择[芯片支持包](https://em-ide.com/zh-cn/docs/modules/chip_pkg)(不是必须)3.构建配置4.[烧录配置](https://e......
  • Python学习—函数篇 面面俱到,细致讲解
    目录1.函数目的2.函数定义3.函数的调用4.函数的形参,实参5.函数的返回值1.返回一个值2.返回多个值3.没有返回值4.返回None6.函数的参数类型1.必需参数2.关键字参数3.默认参数4.可变参数5.关键字可变参数7.匿名函数基本语法示例1.函数目的在编程中,定......
  • 零基础学STM32(二)-新建工程(点亮一个LED灯)
    本项目讲解所用工程均使用stm32f103C8T6芯片HAL库版本。STM32f103参考文档:https://pan.baidu.com/s/1JEtJMZmqU5gk4Nbupe1Apg?pwd=8888CubeMX简介CubeMX是STMicroelectronics(意法半导体)提供的一款免费的配置和初始化工具,用于其STM32微控制器系列。这个工具可以帮助开发者轻......
  • 零基础学STM32(一)-开发软件安装
    本项目讲解所用工程均使用stm32f103C8T6芯片HAL库版本。重点!!!!安装到文件夹路径不能有中文路径软件资源keil5安装包地址:https://pan.baidu.com/s/1IUf6DU20vJC0rrIv3_DBCQ?pwd=8888 cubemx安装包地址:https://pan.baidu.com/s/1beA_TQ8qS593POVwsv-onw?pwd=8888 (各取所需......
  • VScode利用EIDE和cortex-debug进行stm32开发(包括配置以及使用)
    目录前言必要准备第一步:安装vscode插件——EIDEEIDE是什么EIDE的下载EIDE的配置(重点)EIDE功能的简单介绍1.项目的建立:新建项目或者导入项目2.为项目选择芯片支持包(不是必须)3.构建配置4.烧录配置5.项目属性和项目设置6.安装实用工具和打开插件设置第二步:安装vscode插件——cortex-......
  • 嵌入式C++、STM32F103、MQTT、InfluxDB存储和Grafana可视化:工厂设备的实时监控和数据
    1.项目概述随着工业4.0的推进,智能制造已成为制造业发展的必然趋势。本文介绍了一套基于STM32和MQTT协议的小型工厂设备监控系统,可实现对工厂设备的实时监控和数据分析,有效提高生产效率和安全性。系统主要包括三个部分:设备端:使用STM32MCU连接各种传感器,采集设备运行......
  • STM32学习笔记
    DAY1一、嵌入式的概述国内定义:嵌入式就是以应用为中心,以计算机技术为基础,软硬件可裁剪,适用于对于体积、可靠性、功耗、性能等方面有严格要求的专用计算机系统,要求嵌入式开发人员对嵌入式知识体系有清晰的认知。更简单的说,处理桌面PC和服务器之外,所有的控制类设备都是嵌......
  • SPI通信----STM32C8T6+RC522刷卡+0.96寸OLED显示
    1.项目涉及的知识点1.SPI通信在RC522上的刷卡应用2.操作STM32内部FLASH3.IIC在OLED上显示数据的应用4.串口编程及其应用2.STM32与各个模块的管脚接线STM32管脚模块管脚3V3或者5V串口3V3或者5VPA9串口1的RXDPA10串口1的TXDGND串口GND3V3或者5V0.96寸OLED屏的3V3或者5VPB60.96......
  • 蓝牙通信--STM32读取超声波传感器并在手机APP上显示
    1.实物接线本设计主要是用HC-SR04超声波传感器测量距离,通过串口3经过HC-05蓝牙芯片发送到蓝牙调试助手APP上显示。STM32管脚模块管脚3V3超声波VCCPB7超声波ECHOPB6超声波TRIGGND超声波GND3V3串口3VCCPB10串口3RXDPB11串口3TXDGND串口3GND2.涉及的知识点    本......
  • STM32H7基于STM32CubeMX的以太网示例
    本自述文件适用于STM32CubeIDE版本1.9.0和STM32CubeH7版本1.10.0。对于较旧的工具版本,请参阅存储库中的此自述文件的较旧版本基于LwIP和FreeRTOS的简单以太网示例,运行在STNucleo和Discovery板上。这些例子附在ST社区的FAQ文章中。下面也提供了同样的步骤#特性*固定IP地址192......