STM32-通用定时器

目录

通用定时器

功能框图

输入捕获

测量频率        

测量脉宽

PWM输入模式

输出比较

PWM边沿对齐模式

PWM中心对齐模式

初始化结构体

TIM_TimeBaseInitTypeDef:时基结构体

TIM_OC_InitTypeDef:输出比较结构体

TIM_IC_InitTypeDef:输入捕获结构体

通用定时器实验1:输入捕获脉冲宽度

常规配置

TIM5配置

测试环节

实验现象

通用定时器实验2:输出比较PWM呼吸灯

TIM5配置

测试环节

实验现象

通用定时器实验3:输入捕获电容按键

常规配置

TIM5配置

测试环节

实验现象


通用定时器

通用定时器由一个可编程预分频器驱动的16位自动重新加载计数器组成。应用测量输入的脉冲长度信号(输入捕获)产生输出波形(输出比较和PWM)

脉冲长度和波形周期可以从几微秒调制到几毫秒,使用定时器预分频器和RCC时钟控制器预分频器。

输入捕获应用场景:脉冲跳变沿时间测量PWM输入测量

输出比较应用场景:PWM8种模式(常用PWM边沿对齐模式PWM中心对齐模式

功能框图

16位向上、向下、向上/向下自动重装载计数器。

16位可编程预分频器,1~65536。

多达4个独立通道,用于:

        输入捕获

        输出比较

        PWM产生(边沿对齐模式和中心对齐模式)

        单脉冲模式输出

同步电路控制定时器和外部信号,并互连多个定时器。

中断/DMA生成以下事件:

        更新:计数器溢出/下溢,计数器初始化(由软件或内部/外部触发)

        触发事件(计数器启动、停止、初始化或由内部/外部触发计数)

        输入捕获

        输出比较

支持增量(正交)编码器和霍尔传感器电路的定位目的。

触发输入外部时钟或逐周期电流管理。

时基单元包括:

计数器寄存器(TIMx_CNT)

预分频寄存器(TIMx_PSC)

自动重装载寄存器(TIMx_ARR)

计数模式:

向上计数

向下计数

中心对齐:计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件。然后继续从0开始重新计数,以此循环。

TIx为输入通道,需要被测量的信号从定时器的外部引脚TIMx_CH1/2/3/4进入。

当输入的信号存在高频干扰时,需要对输入信号进行滤波,根据采样定律(采样频率必须大于或等于两倍的输入信号),比如输入信号为1M,存在高频信号干扰时就要进行滤波,可以设置采样频率为2M,这样可以在保证采样到有效信号的基础上把高于2M的高频干扰信号过滤掉。

输入滤波器的配置由TIMx_CR1:CKD[1:0]和TIMx_CCMR1/2:ICxF[3:0]控制。根据ICxF位的描述,采样频率Fsample可以由Fck_int(内部时钟)和Fdts(Fck_int经过分频后的频率,分频因子由CKD[1:0]决定,1/2/4分频)分频后的时钟提供。

边沿检测器用来设置信号在捕获时什么边沿有效(上升沿、下降沿、双边沿),具体由TIMx_CCER:CCxP、CCxNP决定。

捕获通道:IC1/2/3/4。每个捕获通道都有对应的捕获寄存器CCR1/2/3/4,当发生捕获时,计数器CNT的值就会被锁存到捕获寄存器中。

输入通道TIx是用来输入信号的,捕获通道ICx是用来捕获输入信号的通道。一个输入通道的信号可以同时输入给两个捕获通道。比如TI1的信号经过滤波和边沿检测器后TI1FP1和TI1FP2可以进入到捕获通道IC1和IC2。输入通道和捕获通道的映射关系具体由TIMx_CCMR:CCxS[1:0]配置。

ICx的输出信号会经过预分频器,用于决定发生多少个事件时进行一次捕获。具体由TIMx_CCMR:ICxPSC配置。如果希望捕获信号的每一个边沿,则不分频。

经过预分频器的信号ICxPS是最终被捕获的信号,当发生捕获时(第一次),计数器CNT的值会被锁存到捕获寄存器TIMx_CCR中,还会产生CCxI中断,相应的中断位CCxIF(在SR寄存器中)会被置位,通过软件或读取CCR的值可以将CCxIF清0。如果发生第二次捕获(即重复捕获,CCR寄存器中已捕获到计数器值且CCxIF标志已置1),则捕获溢出标志位CCxOF(在SR寄存器中)会被置位,CCx_OF只能通过软件清零。

输入捕获

测量频率        

当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器的值读取到value1中。

当出现第二次上升沿时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器TIM_CCRx中,并再次进入捕获中断,在中断服务程序中记录一次捕获,并把捕获寄存器的值读取到value2中,清除捕获记录标志。利用value2-value1的差值就可以算出信号的周期(频率)。

测量脉宽

当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器的值读取到value1中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。

当下降沿到来时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获,并把捕获寄存器的值读取到value2中,清除捕获记录标志。然后把捕获边沿设置为上升沿捕获。

在测量脉宽过程中需要来回切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出时会产生更新中断,我们可以在中断里对溢出进行记录处理。

PWM输入模式

测量脉宽和频率还有一个更简便的方法:PWM输入模式,该模式是输入捕获的特例,只能使用通道1和2,通道3和4不能使用。测量脉宽和频率的方法只使用一个捕获寄存器,PWM输入模式需要占用两个捕获寄存器

当使用PWM输入模式时,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入模式时最多只能使用两个输入通道(TIx)。

以输入通道TI1工作在PWM输入模式为例:

PWM信号由输入通道TI1进入,因为是PWM输入模式,所以信号被分为两路(TI1FP1、TI1FP2)。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入(作为触发输入的那一路信号对应的是周期,另一路信号对应的是占空比)。作为触发输入的那一路信号还需要设置极性(上升沿捕获还是下降沿捕获),一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。

概况:选定输入通道,确定触发信号,然后设置触发性的极性(因为是PWM输入模式,所以另一路由硬件配置,无需软件配置)

当使用PWM输入模式时必须将从模式控制器配置为复位模式(配置寄存器TIMx_SMCR:SMS[2:0]实现),即当我们启动触发信号开始进行捕获时,同时把计数器CNT复位清零。

上图为例,PWM信号由输入通道TI1进入,配置TI1FP1为触发信号,上升沿捕获。

当上升沿到来时IC1和IC2同时捕获,计数器CNT清零;

到了下降沿时IC2捕获,计数器CNT的值被锁存在捕获寄存器TIMx_CCR2中;

到了下一个上升沿到来时IC1捕获,计数器CNT的值被锁存在捕获寄存器TIMx_CCR1中。

其中TIMx_CCR2 + 1测量的是脉宽,TIMx_CCR1 + 1测量的是周期。(注意:TIMx_CCR1和TIMx_CCR2都必须加1,因为计数器是从0开始计数的)

从软件上看,PWM输入模式测量脉宽和周期更容易,缺点是需要占用两个捕获寄存器。

输出比较

输出比较模式共8种,具体由TIMx_CCMR1/2:OCxM[2:0]配置。

PWM输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装载寄存器ARR的值决定,占空比由比较寄存器CCR的值决定。

PWM模式分两种:PWM1、PWM2。

以PWM1模式为例。以计数器CNT计数方向不同还分为边沿对齐模式和中心对齐模式。PWM信号主要都是用来控制电机,一般电机控制用的都是边沿对齐模式,FOC电机一般用的是中心对齐模式。

PWM边沿对齐模式

在递增计数模式下,计数器从0计数到自动重装载值(TIMx_ARR寄存器的内容),然后重新从0开始计数并生成计数器上溢事件。

在边沿对齐模式下,计数器CNT只工作在一种模式:递增或递减模式。

以CNT工作在递增模式为例,ARR=8,CCR=4,CNT从0开始计数。

当CNT < CCR的值时,OCXREF为有效的高电平,此时比较中断寄存器CCxIF置位。

当CCR <= CNT <= ARR时,OCXREF为无效的低电平。

然后CNT又从0开始计数并生成计数器上溢事件,以此循环往复。

PWM中心对齐模式

中心对齐模式下,计数器CNT是工作在递增/递减模式下。

开始时,计数器CNT从0开始计数到自动重装载值减1(ARR-1),生成计数器上溢事件;

然后从自动重装载值开始向下计数到1,生成计数器下溢事件。然后从0开始重新计数,以此往复。

以PWM1模式的中心对齐模式为例,ARR=8,CCR=4。

第一阶段计数器CNT工作在递增模式下从0开始计数。

        当CNT < CCR的值时,OCXREF为有效的高电平。

        当CCR <= CNT <= ARR时,OCXREF为无效的低电平。

第二阶段计数器CNT工作在递减模式下从ARR的值开始计数。

        当CNT > CCR的值时,OCXREF为无效的低电平。

        当1 <= CNT <= CRR时,OCXREF为有效的高电平。

在波形图中可以把波形分为两个阶段,

第一个阶段是计数器CNT工作在递增模式的波形,这个阶段又分为1和2两个阶段。

第一个阶段是计数器CNT工作在递减模式的波形,这个阶段又分为3和4两个阶段。

中心对齐模式下的波形特征是1和3的时间相等,2和4的时间相等。

中心对齐模式又分为中心对齐1/2/3三种,具体由寄存器TIMx_CR1:CMS[1:0]配置。具体的区别是比较中断标志位CCxIF在何时置1。

中心模式1在CNT递减计数时置1,中心模式2在CNT递增计数时置1,中心模式3在CNT递增和递减计数时都置1

初始化结构体

TIM_TimeBaseInitTypeDef:时基结构体

typedef struct
{
    uint16_t TIM_Prescaler;           // 预分频器,基本定时器配置需要
    uint16_t TIM_CounterMode;         // 计数模式,通用定时器配置需要
    uint16_t TIM_Period;              // 定时周期,基本定时器配置需要
    uint16_t TIM_ClockDivision;       // 时钟分频,通用定时器配置需要
    uint8_t TIM_RepetitionCounter;    // 重复计数器
} TIM_TimeBaseInitTypeDef;   

对于TIM_CounterMode,有。

#define TIM_CounterMode_Up                 ((uint16_t)0x0000)    //边沿对齐模式,向上计数
#define TIM_CounterMode_Down               ((uint16_t)0x0010)    //边沿对齐模式,向下计数
 
#define TIM_CounterMode_CenterAligned1     ((uint16_t)0x0020)    //中央对齐模式1,计数器交替向上向下计数。
//产生下溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,只在计数器向下计数时被设置。
 
#define TIM_CounterMode_CenterAligned2     ((uint16_t)0x0040)    //中央对齐模式2,计数器交替向上向下计数。 
//产生上溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,只在计数器向上计数时被设置。
 
#define TIM_CounterMode_CenterAligned3     ((uint16_t)0x0060)    //中央对齐模式3,计数器交替向上向下计数。 
//产生下溢和上溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,在计数器向上和向下计数时均被设置。

对于TIM_ClockDivision,为时钟分频,设置定时器时钟CK_INT频率与死区发生器以及数字滤波器采样时钟频率分频比,可选择1/2/4分频。

TIM_OC_InitTypeDef:输出比较结构体

与TIM_OCxInit函数配合使用完成指定定时器输出通道初始化配置。

typedef struct {
     uint32_t OCMode;        // 比较输出模式,8种,常用PWM1或PWM2
     uint32_t Pulse;         // 比较输出脉冲宽度。0~65535
     uint32_t OCPolarity;    // 比较输出极性。决定着定时器通道有效电平
     uint32_t OCNPolarity;   // 互补输出极性
     uint32_t OCFastMode;    // 比较输出模式快速使能
     uint32_t OCIdleState;   // 空闲状态下比较输出状态
     uint32_t OCNIdleState;  // 空闲状态下比较互补输出状态
 } TIM_OCInitTypeDef;

TIM_IC_InitTypeDef:输入捕获结构体

与HAL_TIM_IC_ConfigChannel函数配合使用完成指定定时器输入通道初始化配置。

如果使用PWM输入模式需要与HAL_TIM_PWM_ConfigChannel函数配合使用完成指定定时器输入通道初始化配置。

typedef struct {
     uint32_t ICPolarity;   // 输入捕获通道的极性选择,上升沿/下降沿/边沿跳变触发捕获事件
     uint32_t ICSelection;  // 输入捕获通道的输入源,捕获通道ICx的信号可来自三个输入通道,分别为TIM_ICSELECTION_DIRECTTI、TIM_ICSELECTION_INDIRECTTI或TIM_ICSELECTION_TRC
     uint32_t ICPrescaler;  // 输入捕获预分频器,1/2/4/8,如果需要捕获输入信号的每个有效边沿,则设置1分频即可
     uint32_t ICFilter;     // 输入捕获滤波器,0x0~0xf。一般不使用滤波器,即设置为0
} TIM_IC_InitTypeDef;

ICSelection为输入捕获通道的输入源。

        当为TIM_ICSELECTION_DIRECTTI时,TI1、TI2、TI3、TI4对应IC1、IC2、IC3、IC4。

        当为TIM_ICSELECTION_DIRECTTI时,TI1、TI2、TI3、TI4对应IC2、IC1、IC4、IC3。

        当为TIM_ICSELECTION_TRC时,TI1、TI2、TI3、TI4对应TRC。

通用定时器实验1:输入捕获脉冲宽度

常规配置

USART:115200-8-N-1,支持重定向输出printf函数,勾选使用C库。

TIM5配置

硬件原理图得知,PA0是KEY1的按键IO,也是TIM5_CH1的IO。

TIM_HandleTypeDef htim5;

void MX_TIM5_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_IC_InitTypeDef sConfigIC = {0};

    htim5.Instance 					= TIM5;
    htim5.Init.Prescaler 			= 71;
    htim5.Init.CounterMode 			= TIM_COUNTERMODE_UP;
    htim5.Init.Period 				= 65535;
    htim5.Init.ClockDivision 		= TIM_CLOCKDIVISION_DIV1;
    htim5.Init.AutoReloadPreload 	= TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
    {
        Error_Handler();
    }

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }

    if (HAL_TIM_IC_Init(&htim5) != HAL_OK)
    {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger 	= TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode 		= TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }

    sConfigIC.ICPolarity 	= TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection 	= TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler 	= TIM_ICPSC_DIV1;
    sConfigIC.ICFilter 		= 0;
    if (HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
    {
        Error_Handler();
    }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (tim_baseHandle->Instance == TIM5)
    {
        __HAL_RCC_TIM5_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
		
        /**TIM5 GPIO Configuration
        PA0-WKUP     ------> TIM5_CH1
        */
        GPIO_InitStruct.Pin 	= GPIO_PIN_0;
        GPIO_InitStruct.Mode 	= GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull 	= GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        HAL_NVIC_SetPriority(TIM5_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(TIM5_IRQn);
    }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)
{
    if (tim_baseHandle->Instance == TIM5)
    {
        __HAL_RCC_TIM5_CLK_DISABLE();

        /**TIM5 GPIO Configuration
        PA0-WKUP     ------> TIM5_CH1
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);

        /* TIM5 interrupt Deinit */
        HAL_NVIC_DisableIRQ(TIM5_IRQn);
    }
}

测试环节

typedef struct              
{   
	uint8_t   ucFinishFlag;   // 捕获结束标志位
	uint8_t   ucStartFlag;    // 捕获开始标志位
	uint16_t  usCtr;          // 捕获寄存器的值
	uint16_t  usPeriod;       // 自动重装载寄存器更新标志 
}STRUCT_CAPTURE; 			  // 定时器输入捕获用户自定义变量结构体声明
STRUCT_CAPTURE TIM_ICUserValueStructure = {0, 0, 0, 0};

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    TIM_ICUserValueStructure.usPeriod++;
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    TIM_IC_InitTypeDef IC_Config;

    if (TIM_ICUserValueStructure.ucStartFlag == 0)	// 上升沿捕获,开始捕获
    {
        __HAL_TIM_SET_COUNTER(htim, 0); 			// 清零定时器计数
        TIM_ICUserValueStructure.usPeriod 	= 0;
        TIM_ICUserValueStructure.usCtr	 	= 0;

        // 配置输入捕获参数,主要是修改触发电平
        IC_Config.ICPolarity 	= TIM_INPUTCHANNELPOLARITY_FALLING;
        IC_Config.ICSelection 	= TIM_ICSELECTION_DIRECTTI;
        IC_Config.ICPrescaler 	= TIM_ICPSC_DIV1;
        IC_Config.ICFilter 		= 0;
        HAL_TIM_IC_ConfigChannel(&htim5, &IC_Config, TIM_CHANNEL_1);
		
        // 清除TIM捕获/比较1中断标志位
        __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
		
        // 启动输入捕获并开启中断
        HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
		
        TIM_ICUserValueStructure.ucStartFlag = 1;
    }
    else											// 下降沿捕获,结束捕获
    {
        // 获取定时器计数值
        TIM_ICUserValueStructure.usCtr = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1);
		
        // 配置输入捕获参数,主要是修改触发电平
        IC_Config.ICPolarity 	= TIM_INPUTCHANNELPOLARITY_RISING;
        IC_Config.ICSelection 	= TIM_ICSELECTION_DIRECTTI;
        IC_Config.ICPrescaler 	= TIM_ICPSC_DIV1;
        IC_Config.ICFilter 		= 0;
        HAL_TIM_IC_ConfigChannel(&htim5, &IC_Config, TIM_CHANNEL_1);

        // 清除中断标志位
        __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
		
        // 启动输入捕获并开启中断
        HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
		
        TIM_ICUserValueStructure.ucStartFlag 	= 0;
        TIM_ICUserValueStructure.ucFinishFlag 	= 1;
    }
}

void test(void)
{
	uint32_t time, TIM_PscCLK;
	
	初始化
	
	// TIM 计数器的驱动时钟
    TIM_PscCLK = HAL_RCC_GetHCLKFreq() / 71;

    /* 启动定时器 */
    HAL_TIM_Base_Start_IT(&htim5);
    /* 启动定时器通道输入捕获并开启中断 */
    HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
	
	while (1)
    {
        /* 完成测量高电平脉宽 */
        if (TIM_ICUserValueStructure.ucFinishFlag == 1)
        {
            /* 计算高电平计数值 */
            time = TIM_ICUserValueStructure.usPeriod * 65535 + TIM_ICUserValueStructure.usCtr;
            /* 打印高电平脉宽时间 */
            printf("测得高电平脉宽时间:%d.%d s\n", time / TIM_PscCLK, time % TIM_PscCLK);
            TIM_ICUserValueStructure.ucFinishFlag = 0;
        }
    }
}

实验现象

通用定时器实验2:输出比较PWM呼吸灯

TIM5配置

硬件原理图得知,PB0是LED_G的IO,也是TIM3_CH3的IO。

测试环节

/* LED亮度等级(PWM表,指数曲线),此表使用工程目录下的python脚本index_wave.py生成 */
uint16_t indexWave[] =
{
    1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 11, 13,
    15, 17, 19, 22, 25, 28, 32, 36, 41, 47, 53, 61, 69, 79, 89, 102,
    116, 131, 149, 170, 193, 219, 250, 284, 323, 367, 417, 474, 539, 
	613, 697, 792, 901, 1024, 1024, 901, 792, 697, 613, 539, 474, 417, 
	367, 323, 284, 250, 219, 193, 170, 149, 131, 116, 102, 89, 79, 69, 
	61, 53, 47, 41, 36, 32, 28, 25, 22, 19, 17, 15, 13, 11, 10, 9, 8, 
	7, 6, 5, 5, 4, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1
};

//计算PWM表有多少个元素
uint16_t POINT_NUM = sizeof(indexWave) / sizeof(indexWave[0]);

// 定时器捕获回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint8_t pwm_index = 1;		/* 用于PWM查表 */
    static uint8_t period_cnt = 0;		/* 用于计算周期数 */

    period_cnt++;

    /* 若输出的周期数大于30,输出下一种脉冲宽的PWM波 */
    if (period_cnt >= 30)
    {
        /* 根据PWM表修改定时器的比较寄存器值 */
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, indexWave[pwm_index]);

        /* 标志PWM表的下一个元素 */
        pwm_index++;

        /* 若PWM脉冲表已经输出完成一遍,重置PWM查表标志 */
        if (pwm_index >=  POINT_NUM)
        {
            pwm_index = 0;
        }

        /* 重置周期计数标志 */
        period_cnt = 0;
    }
}

void test(void)
{
	初始化
	
	/* 启动通道PWM输出 */
    HAL_TIM_Base_Start_IT(&htim3);
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
	
	while(1){}
}

实验现象

绿色LED有规律的亮灭,经典呼吸灯。

通用定时器实验3:输入捕获电容按键

常规配置

USART1:115200-8-N-1,支持重定向输出printf函数,勾选使用C库。

蜂鸣器:PA8,高电平响。

TIM5配置

硬件原理图得知,PA1是电容按键IO,也是TIM5_CH2的IO。

TIM_HandleTypeDef htim5;

void MX_TIM5_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_IC_InitTypeDef sConfigIC = {0};

    htim5.Instance 					= TIM5;
    htim5.Init.Prescaler 			= 47;
    htim5.Init.CounterMode 			= TIM_COUNTERMODE_UP;
    htim5.Init.Period 				= 0xffff;
    htim5.Init.ClockDivision 		= TIM_CLOCKDIVISION_DIV1;
    htim5.Init.AutoReloadPreload 	= TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
    {
        Error_Handler();
    }

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
    {
        Error_Handler();
    }

    if (HAL_TIM_IC_Init(&htim5) != HAL_OK)
    {
        Error_Handler();
    }

    sMasterConfig.MasterOutputTrigger 	= TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode 		= TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
    {
        Error_Handler();
    }

    sConfigIC.ICPolarity 	= TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection 	= TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler 	= TIM_ICPSC_DIV1;
    sConfigIC.ICFilter 		= 3;
    if (HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
    {
        Error_Handler();
    }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (tim_baseHandle->Instance == TIM5)
    {
        __HAL_RCC_TIM5_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
		
        /**TIM5 GPIO Configuration
        PA1     ------> TIM5_CH2
        */
        GPIO_InitStruct.Pin 	= GPIO_PIN_1;
        GPIO_InitStruct.Mode 	= GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull 	= GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)
{
    if (tim_baseHandle->Instance == TIM5)
    {
        __HAL_RCC_TIM5_CLK_DISABLE();

        /**TIM5 GPIO Configuration
        PA1     ------> TIM5_CH2
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1);
    }
}

测试环节

//保存没按下时定时器计数值
__IO uint16_t tpad_default_val = 0;

/****************************************************
 * 得到定时器捕获值
 * 如果超时,则直接返回定时器的计数值.
 *****************************************************/
static uint16_t TPAD_Get_Val(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/* 设置引脚输出为低电平 */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
	
	/* 设定电容按键对应引脚IO编号 */
    GPIO_InitStruct.Pin 	= GPIO_PIN_1;
    GPIO_InitStruct.Mode 	= GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed 	= GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    HAL_Delay(5);
	
    __HAL_TIM_SET_COUNTER(&htim5, 0); 								// 清零定时器计数
    __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE | TIM_FLAG_CC2); 	// 清除中断标志

	GPIO_InitStruct.Pin 	= GPIO_PIN_1;
	GPIO_InitStruct.Mode 	= GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull 	= GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	HAL_TIM_IC_Start(&htim5, TIM_CHANNEL_2);

    while (__HAL_TIM_GET_FLAG(&htim5, TIM_FLAG_CC2) == RESET)
    {
        uint16_t count;
        count = __HAL_TIM_GET_COUNTER(&htim5);
        if (count > (0xFFFF - 500))
        {
            return count;    //超时了,直接返回CNT的值
        }
    }
	
    return HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_2);
}

/****************************************************
 *
 * 读取n次,取最大值
 * n:连续获取的次数
 * 返回值:n次读数里面读到的最大读数值
 *
 *****************************************************/
static uint16_t TPAD_Get_MaxVal(uint8_t n)
{
    uint16_t temp = 0;
    uint16_t res = 0;

    while (n--)
    {
        temp = TPAD_Get_Val(); //得到一次值

        if (temp > res)
        {
            res = temp;
        }
    }

    return res;
}

/********************************************************
*
* 初始化触摸按键
* 获得空载的时候触摸按键的取值.
* 返回值:0,初始化成功;1,初始化失败
*
*********************************************************/
uint8_t TPAD_Init(void)
{
    uint16_t buf[10];
    uint32_t temp = 0;
    uint8_t i, j;

    /* 连续读取10次 */
    for (i = 0; i < 10; i++)
    {
        buf[i] = TPAD_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);

    /* 初始化遇到超过0xffff/2的数值,不正常! */
    if (tpad_default_val > 0xffff / 2)
    {
        return 1;
    }

    return 0;
}

/*******************************************************************************
*
* 扫描触摸按键
* mode:0,不支持连续触发(按下一次必须松开才能按下一次);1,支持连续触发(可以一直按下)
* 返回值:0,没有按下;1,有按下;
*
*******************************************************************************/
//阈值:捕获时间必须大于(tpad_default_val + TPAD_GATE_VAL),才认为是有效触摸.
#define TPAD_GATE_VAL 	100

uint8_t TPAD_Scan(uint8_t mode)
{
    //0,可以开始检测;>0,还不能开始检测
    static uint8_t keyen = 0;
    //扫描结果
    uint8_t res = 0;
    //默认采样次数为3次
    uint8_t sample = 3;
    //捕获值
    uint16_t rval;

    if (mode)
    {
        //支持连按的时候,设置采样次数为6次
        sample = 6;
        //支持连按
        keyen = 0;
    }

    /* 获取当前捕获值(返回 sample 次扫描的最大值) */
    rval = TPAD_Get_MaxVal(sample);
    /* printf打印函数调试使用,用来确定阈值TPAD_GATE_VAL,在应用工程中应注释掉 */
    printf("scan_rval=%d\r\n", rval);

    //大于tpad_default_val+TPAD_GATE_VAL,且小于10倍tpad_default_val,则有效
    if (rval > (tpad_default_val + TPAD_GATE_VAL) && rval < (10 * tpad_default_val))
    {
        //keyen==0,有效
        if (keyen == 0)
        {
            res = 1;
        }

        keyen = 3;				//至少要再过3次之后才能按键有效
    }

    if (keyen)
    {
        keyen--;
    }

    return res;
}

void test(void)
{
	初始化
	
	TPAD_Init();
	while(1)
	{
		if (TPAD_Scan(1))
		{
			HAL_GPIO_WritePin(Beep_GPIO_Port, Beep_Pin, GPIO_PIN_SET);
			HAL_Delay(30);
			HAL_GPIO_WritePin(Beep_GPIO_Port, Beep_Pin, GPIO_PIN_RESET);
		}
	}
}

实验现象

按下电容按键,蜂鸣器发出滴滴响声。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值