STM32学习5——定时器

一、概述

STM32F1系列共有8定时器,2个高级定时器(TIM1/8),4个通用定时器(TIM2/3/4/5),2个基本定时器(TIM6/7)。如图定时器分类
基本定时器是16位只可向上计数的定时器,只能定时,无外部IO。
通用定时器是16位可向上、向下计数的定时器,可定时、输入捕获、输出比较,每个定时器有4个外部输出。
高级定时器是16位可向上、向下计数的定时器,可定时、输入捕获、输出比较、互补输出信号等,每个定时器有8个外部输出。

二、通用定时器定时中断

我选用了TIM2,通道3,查表知对应引脚位PB10
在这里插入图片描述
记得把定时器TIM2打开,时钟源是内部时钟,定时器中断也要配置。
时钟预分频器71,自动重装载寄存器ARR为1000,这样中断一次的时间为1ms。
定义一个全局变量

uint16_t time=0;//计数,time加到1000,我们翻转一次灯。1ms*1000=1s

main函数中开启中断

HAL_TIM_Base_Start_IT(&htim2);//开定时器中断,函数原型在tm32f1xx_hal_tim.c中

其他的操作在中断处理函数中,重写中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim==&htim2)//判断是定时器2产生的中断
	{
		time++;
		if(time==1000){
			time=0;//1ms*1000=1s,时间到,翻转灯
			HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);
		}
	}
}

实验现象是:绿灯1s闪烁一次.

三、PWM输出

脉冲宽度调制PWM。除了基本定时器6、7,其他定时器都可以产生PWM输出。且高级定时器可产生7路,通用定时器可产生4路,STM最多同时可产生30路PWM输出。
一路输出实验:
TIM3通道2,映射到PB5,产生PWM输出,改变占空比,生成呼吸灯。
全局变量

unsigned int ledpwmval=0;//控制占空比;
unsigned char dir=1;//控制方向,1:暗->亮,2:亮->暗

main中,while前

HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_2);

while中

HAL_Delay(10);
		if(dir){
			ledpwmval+=2;
		}
		else{
			ledpwmval-=2;
		}
		if(ledpwmval>300){
			dir=0;
		}
		if(ledpwmval==0){
			dir=1;
		}
		 __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,ledpwmval);

用ledpwmval来控制占空比,dir为1时,从暗到亮,0时从亮到灭。ledpwmval不断增加到300,在从300减少到0,灯也跟着从暗到亮再从亮到暗。取300是因为往后的led亮度变化就不大了,最大可以到899(前边设置的自动重装值)。
四路输出实验
还是用TIM3,同时开启四路PWM输出,产生呼吸灯。
通道1、2、3、4都开启
main中,while前(开启PWM)

HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_4);

while中

int i;
for(i=0;i<1600;i+=4)
{
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,i);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,i);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_4,i);
	HAL_Delay(10);
}
for(i=1600;i>0;i-=4)
{
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_2,i);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,i);
	__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_4,i);
	HAL_Delay(10);
}
HAL_Delay(1000);

四、输入捕获

输入捕获一般用在2方面,一是脉冲宽度测量,二是频率测量。
脉冲宽度测量:
配置TIM5的通道1,时钟预分频器71,自动重装载寄存器ARR为0xffff。
开启中断后,设置上升沿捕获,产生中断后,读取定时器的值记下来(m1)。
然后改变捕获极性,改为下降沿捕获,产生中断后记下定时器值为m2。
高电平脉冲时间就为m2-m1。
但注意一个问题,如果脉冲宽度太长,超过了定时器计时最大数,就会产生溢出中断,必须在中断里边进行处理。
加一个记录的变量var。产生一次中断,加1。并清除中断。这时高脉冲时间就是var*arr+(m2-m1)。
全局变量

uint32_t capture_Buf[3]={0};//存放计数值
uint8_t capture_Cnt=0;//状态标志位,1为上升沿,2为下降沿,3开始算时间
uint32_t high_time;//高电平的时间
uint8_t timICCount=0;//定时器溢出次数,脉冲过长,超过了定时器最大时间

主函数中的while

switch(capture_Cnt){
			
			case 0://开启上升沿捕获
				capture_Cnt++;
				//改变为捕获下降沿
				__HAL_TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);
				//开启捕获
				HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
				break;
			
			case 3://结束捕获,计算高电平时间
				if(capture_Buf[1]>capture_Buf[0])
				{
				
				}
				high_time=timICCount*(0xffff)+(capture_Buf[1]-capture_Buf[0]);
				//high_time=capture_Buf[1]-capture_Buf[0];
				//显示到串口
				//HAL_UART_Transmit_IT(&huart1, (uint8_t *)high_time, 0xffff);
				printf("high_time:%d",high_time);
				printf("time is %d.%d s。\r\n",high_time/1000000,high_time%1000000);
				HAL_Delay(1000);	
				capture_Cnt=0;//清空标志位
				capture_Buf[0]=0;
				capture_Buf[1]=0;
				break;
		}

定时器中断回调函数中

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	//如果脉宽过长,定时器溢出中断,进行处理
	if(htim==&htim5)
	{
			if(HAL_TIM_IC_GetState(&htim5)!=HAL_TIM_STATE_RESET)
		{
			timICCount++;
			//__HAL_TIM_CLEAR_IT(&htim5, TIM_IT_UPDATE);
			__HAL_TIM_DISABLE_IT(&htim5, TIM_IT_UPDATE);
		}
		switch(capture_Cnt)
		{
			case 1://捕捉到上升沿时计数
				//capture_Buf[0]= __HAL_TIM_GET_COMPARE(&htim5, TIM_CHANNEL_1);//先获取值
				capture_Buf[0]= HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1);
				__HAL_TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);//改变捕获的方向
				capture_Cnt++;
				break;
			case 2:
				//捕获到下降沿
				//capture_Buf[1]= __HAL_TIM_GET_COMPARE(&htim5, TIM_CHANNEL_1);//先获取值
				capture_Buf[1]= HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1);
				HAL_TIM_IC_Stop_IT(&htim5, TIM_CHANNEL_1);//停止捕获
				__HAL_TIM_DISABLE_IT(&htim5, TIM_CHANNEL_1);
				capture_Cnt++;//3,此时转回主函数开始计算
		}
	}
}

这里边还用到了串口打印,要想用printf,需要将其重定向。

#ifdef __GNUC__
  /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
 
  return ch;
}

这是就可以将结果打印出来。
实现功能:
按下按键(PA0),串口输出按下的时间,即高电平脉冲时间。

脉冲宽度测量是利用捕获上升沿、下降沿的时间差,那频率测量应该可以利用两个接连的上升沿时间差。
或者同时测量,改变捕获极性,记录下3个时间m1,m2,m3(这里先不考虑定时溢出),m3-m1就是一个周期。
我没有具体实现,想来应该和脉冲宽度测量差不多。

五、电容按键输入捕获

电容按键,顾名思义,是利用电容充放电效应的按键。此处的按键是触摸按键(TPAD)。
初始状态电容无电(为保证无电,要先进行放电),与电容开关相连的开关按下时,利用上拉电阻,使电容上下有电压差,电容开始充电,同时开启PWM输入捕获,当充满后,捕获到上升沿,充电完成,完成捕获。
每次复位时,先进行一次捕获(没触摸时),记下值为default,当捕捉到上升沿时,通过与default对比,来判断是否有捕获发生。
触摸按键板载在PA1.开启PA1的定时器TIM5-CH2,同时开启led-PB0。
bsp_touchpad.h中

#ifndef __TOUCHPAD_KEY_H__
#define __TOUCHPAD_KEY_H__

/* 包含头文件 ----------------------------------------------------------------*/

#include "stm32f1xx_hal.h"

/* 类型定义 ------------------------------------------------------------------*/

/* 宏定义 --------------------------------------------------------------------*/
#define TOUCHPAD_TIMx                        TIM5
#define TOUCHPAD_TIM_RCC_CLK_ENABLE()        __HAL_RCC_TIM5_CLK_ENABLE()
#define TOUCHPAD_TIM_RCC_CLK_DISABLE()       __HAL_RCC_TIM5_CLK_DISABLE()

#define TOUCHPAD_GPIO_RCC_CLK_ENABLE()       __HAL_RCC_GPIOA_CLK_ENABLE()
#define TOUCHPAD_GPIO_PIN                    GPIO_PIN_1
#define TOUCHPAD_GPIO                        GPIOA
#define TOUCHPAD_TIM_CHANNEL                 TIM_CHANNEL_2
#define TOUCHPAD_TIM_FLAG_CCR                TIM_FLAG_CC2

// 定义定时器预分频,定时器实际时钟频率为:72MHz/(TOUCHPAD_TIMx_PRESCALER+1)
#define TOUCHPAD_TIM_PRESCALER              47 //1.5MHz

// 定义定时器周期
#define TOUCHPAD_TIM_ARR                     0xFFFF

/* 扩展变量 ------------------------------------------------------------------*/
extern TIM_HandleTypeDef htimx;

/* 函数声明 ------------------------------------------------------------------*/
uint8_t TOUCHPAD_Init(void);
uint8_t TOUCHPAD_Scan(uint8_t mode);

#endif        /* __TOUCHPAD_KEY_H__ */

bsp_touchpad.c中

#include "bsp_touchpad.h"
/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
//触摸的门限值,也就是必须大于tpad_default_val+TOUCHPAD_GATE_VAL,才认为是有效触摸.
#define TOUCHPAD_GATE_VAL         80

/* 私有变量 ------------------------------------------------------------------*/
TIM_HandleTypeDef htimx;
__IO  uint16_t tpad_default_val=0;/* 空载的时候(没有手按下),计数器需要的时间  */

/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/
/**
  * 函数功能: 通用定时器初始化并配置通道PWM输出
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 无
  */
static void TOUCHPAD_TIMx_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
  TIM_IC_InitTypeDef sConfigIC;
  
  htimx.Instance = TOUCHPAD_TIMx;
  htimx.Init.Prescaler = TOUCHPAD_TIM_PRESCALER;
  htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
  htimx.Init.Period = TOUCHPAD_TIM_ARR;
  htimx.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_Base_Init(&htimx);

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  HAL_TIM_ConfigClockSource(&htimx, &sClockSourceConfig);

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  HAL_TIMEx_MasterConfigSynchronization(&htimx, &sMasterConfig);
  
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 3;
  HAL_TIM_IC_ConfigChannel(&htimx, &sConfigIC, TOUCHPAD_TIM_CHANNEL);
}

/**
  * 函数功能: 基本定时器硬件初始化配置
  * 输入参数: htim_base:基本定时器句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  if(htim_base->Instance==TOUCHPAD_TIMx)
  {
    /* 基本定时器外设时钟使能 */
    TOUCHPAD_TIM_RCC_CLK_ENABLE();
    /* 定时器通道引脚时钟使能 */
    TOUCHPAD_GPIO_RCC_CLK_ENABLE();
    /* 定时器通道引脚配置:捕获功能设置为输入模式 */
    GPIO_InitStruct.Pin = TOUCHPAD_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    HAL_GPIO_Init(TOUCHPAD_GPIO, &GPIO_InitStruct);
  }
}

/**
  * 函数功能: 基本定时器硬件反初始化配置
  * 输入参数: htim_base:基本定时器句柄类型指针
  * 返 回 值: 无
  * 说    明: 该函数被HAL库内部调用
  */
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
{
  if(htim_base->Instance==TOUCHPAD_TIMx)
  {
    /* 基本定时器外设时钟禁用 */
    TOUCHPAD_TIM_RCC_CLK_DISABLE();
   
    HAL_GPIO_DeInit(TOUCHPAD_GPIO, TOUCHPAD_GPIO_PIN);
  }
}

/**
  * 函数功能: 复位一次,为电容按键放电
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
static void TOUCHPAD_Reset(void)
{
   /* 定义IO硬件初始化结构体变量 */
  GPIO_InitTypeDef GPIO_InitStruct;
       
        /* 使能电容按键引脚对应IO端口时钟 */  
  TOUCHPAD_GPIO_RCC_CLK_ENABLE();
  
  /* 设置引脚输出为低电平 */
  HAL_GPIO_WritePin(TOUCHPAD_GPIO, TOUCHPAD_GPIO_PIN, GPIO_PIN_RESET);  
  
  /* 设定电容按键对应引脚IO编号 */
  GPIO_InitStruct.Pin = TOUCHPAD_GPIO_PIN;
  /* 设定电容按键对应引脚IO为输出模式 */
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  /* 设定电容按键对应引脚IO操作速度 */
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  /* 初始化电容按键对应引脚IO */
  HAL_GPIO_Init(TOUCHPAD_GPIO, &GPIO_InitStruct);  

        HAL_Delay(5);

        __HAL_TIM_SET_COUNTER(&htimx,0); // 清零定时器计数  
  __HAL_TIM_CLEAR_FLAG(&htimx, TIM_FLAG_UPDATE|TIM_FLAG_CC2);//清除中断标志
  
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(TOUCHPAD_GPIO, &GPIO_InitStruct);
  
  HAL_TIM_IC_Start(&htimx,TOUCHPAD_TIM_CHANNEL);  
}

/**
  * 函数功能: 得到定时器捕获值,如果超时,则直接返回定时器的计数值.
  * 输入参数: 无
  * 返 回 值: uint16_t:定时器捕获值
  * 说    明:无
  */
static uint16_t TOUCHPAD_Get_Val(void)
{
        TOUCHPAD_Reset();
        while(__HAL_TIM_GET_FLAG(&htimx,TOUCHPAD_TIM_FLAG_CCR)==RESET)
  {   
    uint16_t count;
    count=__HAL_TIM_GET_COUNTER(&htimx);
                if(count>(TOUCHPAD_TIM_ARR-500))
      return count;//超时了,直接返回CNT的值
        };       
        return HAL_TIM_ReadCapturedValue(&htimx,TOUCHPAD_TIM_CHANNEL);          
}

/**
  * 函数功能: 读取n次,取最大值
  * 输入参数: n:连续获取的次数
  * 返 回 值: n次读数里面读到的最大读数值
  * 说    明:无
  */
static uint16_t TOUCHPAD_Get_MaxVal(uint8_t n)
{
        uint16_t temp=0;
        uint16_t res=0;
        while(n--)
        {
                temp=TOUCHPAD_Get_Val();//得到一次值
                if(temp>res)res=temp;
        };
        return res;
}  

/**
  * 函数功能: 初始化触摸按键,获得空载的时候触摸按键的取值.
  * 输入参数: 无
  * 返 回 值: 0,初始化成功;1,初始化失败
  * 说    明:无
  */
uint8_t TOUCHPAD_Init(void)
{
        uint16_t buf[10];
        uint16_t temp;
        uint8_t i,j;
  /* 以1.5Mhz的频率计数  */
        TOUCHPAD_TIMx_Init();
  HAL_TIM_IC_Start(&htimx,TOUCHPAD_TIM_CHANNEL);
  
  /* 连续读取10次 */
        for(i=0;i<10;i++)
        {                                 
                buf[i]=TOUCHPAD_Get_Val();
                HAL_Delay(10);            
        }
  /* 排序 */
        for(i=0;i<9;i++)
        {
                for(j=i+1;j<10;j++)
                {
      /* 升序排列 */
                        if(buf[i]>buf[j])
                        {
                                temp=buf[i];
                                buf[i]=buf[j];
                                buf[j]=temp;
                        }
                }
        }
        temp=0;
  /* 取中间的6个数据进行平均 */
        for(i=2;i<8;i++)temp+=buf[i];
        tpad_default_val=temp/6;
        printf("tpad_default_val:%d\r\n",tpad_default_val);       
  /* 初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常! */
        if(tpad_default_val>TOUCHPAD_TIM_ARR/2)return 1;
        return 0;                                                                           
}

/**
  * 函数功能: 扫描触摸按键
  * 输入参数: mode:0,不支持连续触发(按下一次必须松开才能按下一次);1,支持连续触发(可以一直按下)
  * 返 回 值: 0,没有按下;1,有按下;       
  * 说    明:无
  */
uint8_t TOUCHPAD_Scan(uint8_t mode)
{
        static uint8_t keyen=0;        //0,可以开始检测;>0,还不能开始检测         
        uint8_t res=0;
        uint8_t sample=3;                //默认采样次数为3次         
        uint16_t rval;
        if(mode)
        {
                sample=6;        //支持连按的时候,设置采样次数为6次
                keyen=0;        //支持连按          
        }
        rval=TOUCHPAD_Get_MaxVal(sample);
        printf("scan_rval=%d\n",rval);
        if(rval>(tpad_default_val+TOUCHPAD_GATE_VAL))//大于tpad_default_val+TPAD_GATE_VAL,有效
        {                                               
                  rval=TOUCHPAD_Get_MaxVal(sample);                 
                if((keyen==0)&&(rval>(tpad_default_val+TOUCHPAD_GATE_VAL)))//大于tpad_default_val+TPAD_GATE_VAL,有效
                {
                        res=1;
                }                                                                  
                keyen=5;                                //至少要再过5次之后才能按键有效   
        }else if(keyen>2)keyen=2;         //如果检测到按键松开,则直接将次数将为2,以提高响应速度
        if(keyen)keyen--;                                                                                                                                                         
        return res;
}  

主函数中

TOUCHPAD_Init();
  while (1)
  {
		if(TOUCHPAD_Scan(0))      //成功捕获一次上升沿
		{
			HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin);
		}
  }

实现功能:
用手触碰电容按键,灯亮,再次触碰,灯灭。一直循环。

至此,TIM定时器学习正式完成,磕磕绊绊,实验基本做完,理论理解得差不多,关键要提高动手能力,学会加到其他器件上。加油嘞!持续努力着!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值