一、概述
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定时器学习正式完成,磕磕绊绊,实验基本做完,理论理解得差不多,关键要提高动手能力,学会加到其他器件上。加油嘞!持续努力着!