STM32-笔记20-测量按键按下时间

1、按键按下的时间-思路

我们先检测下降沿信号,检测到以后,在回调函数里切换成检测上升沿信号,当两个信号都检测到的时候,这段时间就是按键按下的时间,如图所示:=>N*(ARR+1)+CCRx的值

N是在这段时间内,可能会发生的N次溢出。CCRx是寄存器中的计数,ARR打满65536-1

N个重装载值+CCRx的值

2、实验目的

  • 使 用 定 时 器 2 通 道 2 来 捕 获 按 键 2 按 下 时 间 , 并 通 过 串 口 打 印 。
  • 计 一 个 数 的 时 间:1 u s , P S C = 7 1 , A R R = 6 5 5 3 5
  • 下 降 沿 捕 获 、 输 入 通 道 2 映 射 在 TI2 上 、 不 分 频 、 不 滤 波

为什么要使用定时器2通道2来捕获按键2?

这个板子只有两个按键。一个是key1,一个是key2,分别对应A0和A1引脚

原理图

产品手册(17页)

在这里可以看到,KEY1是接的定时器2通道1的ETR引脚,通过下图我们可以看出来,ETR引脚是作为一个输入源的,而我们这里是想要一个通道,所以这里使用KEY2

中文参考手册(254页)

1s = 1000 000us

P S C = 7 1

PSC+1=72

72/72MHZ

1个时间周期 = 1/1000 000 = 1us

这里使用1us来表示记一个数的时,也可以更小,A R R = 6 5 5 3 5,直接把ARR拉满

上面是对时基单元配置

下面是对通道的配置

下 降 沿 捕 获 、 输 入 通 道 2 映 射 在 TI2 上 、 不 分 频 、 不 滤 波

滤波器先不要,对边沿检测时,先对下降沿检测,然后迅速在回调函数中对上升沿进行检测。

分频器不分频

3、实现输入捕获功能

复制项目文件19,重命名为20-实现捕获功能

打开项目文件

创建文件夹ic

加载文件

编译

编译

编译

代码如下:

main.c

cpp 复制代码
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "ic.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();//初始化led灯
    uart1_init(115200);
    //printf("hello word!\r\n");
    ic_init(72-1,65536-1);

    while(1)
    { 

    }
}

ic.c

cpp 复制代码
#include "ic.h"
#include "stdio.h"

TIM_HandleTypeDef ic_handle = {0};

//初始化输入捕获函数
void ic_init(uint16_t psc,uint16_t arr)
{
    TIM_IC_InitTypeDef ic_config = {0};
    ic_handle.Instance = TIM2;//定时器2
    ic_handle.Init.Prescaler = psc;
    ic_handle.Init.Period = arr;
    ic_handle.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
    HAL_TIM_IC_Init(&ic_handle);
    
    ic_config.ICFilter = 0;//过滤器0,也就是不要过滤器
    ic_config.ICPolarity = TIM_ICPOLARITY_FALLING;//下降沿捕获
    ic_config.ICPrescaler = TIM_ICPSC_DIV1;//每次在捕获输入上检测到边缘时执行捕获
    ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;//输入捕获触发信号源直接连接到对应通道的输入捕获引脚,这意味着输入捕获触发信号直接作用于定时器的输入捕获电路,不需要通过其他中间寄存器或外部电
    HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_config,TIM_CHANNEL_2);
    
    __HAL_TIM_ENABLE_IT(&ic_handle,TIM_IT_UPDATE);//更新中断
    HAL_TIM_IC_Start_IT(&ic_handle,TIM_CHANNEL_2);
    
}

//初始化MSP函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        GPIO_InitTypeDef gpio_initstruct;
        
        __HAL_RCC_TIM2_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        gpio_initstruct.Mode = GPIO_MODE_INPUT;//复式推挽输出
        gpio_initstruct.Pin = GPIO_PIN_1;//引脚1
        gpio_initstruct.Pull = GPIO_PULLUP;//上拉输出
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;//高速
        
        HAL_GPIO_Init(GPIOA,&gpio_initstruct);
        
        HAL_NVIC_SetPriority(TIM2_IRQn,2,2);//外部中断号,抢占优先级/响应优先级
        HAL_NVIC_EnableIRQ(TIM2_IRQn);
    }
    
}

//中断服务函数
void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&ic_handle);
}

//回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    printf("捕获一个下降沿\r\n");
}

ic.h

cpp 复制代码
#ifndef __IC_H__
#define __IC_H__
#include "sys.h"

void ic_init(uint16_t psc,uint16_t arr);

#endif

在ic.c函数中

ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;//输入捕获触发信号源直接连接到对应通道的输入捕获引脚,这意味着输入捕获触发信号直接作用于定时器的输入捕获电路,不需要通过其他中间寄存器或外部电

如果

ic_config.ICSelection =TIM_ICSELECTION_INDIRECTTI;//配置输入捕获通道的输入信号选择为间接定时器输入模式。

TIM Input 1 被选择连接到 IC2。

TIM Input 2 被选择连接到 IC1。

TIM Input 3 被选择连接到 IC4。

TIM Input 4 被选择连接到 IC3。

如果这里的ic_config.ICSelection设置成下面这个参数

ic_config.ICSelection =TIM_ICSELECTION_INDIRECTTI;

则摁KEY1才会响应按键

这是因为定时器2则连接到通道1中

在串口助手中摁KEY2会显示

4、实现一次完整的按键动作

复制项目文件20-实现捕获功能,重命名21-捕获一次完整的按键动作

在上面实现了捕获下降沿,在这里将会实现捕获上升沿,整合就会实现一次完整的按键动作。

具体流程如下:

succeed_flag:是否发生了一次完整的按键动作标志位

falling_flag:下降沿标志,当falling_flag = 0时(默认情况下),代表下降沿捕获成功;

当falling_flag = 1时(默认情况下),代表上升沿捕获成功;

这里用到两个函数

TIM_RESET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2);//这行代码的作用是重置 定时器捕获通道2的触发极性。

TIM_SET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2, TIM_ICPOLARITY_RISING);//这行代码的意思是:为ic_handle所指向的定时器的第二个通道配置输入捕获极性为上升沿(即输入信号从低电平变为高电平时)。

在STM32的HAL库中,TIM_RESET_CAPTUREPOLARITY函数用于重置定时器的捕获通道的触发极性。具体来说,这个函数会将指定通道的触发极性设置为默认值,通常是上升沿或下降沿。

IM_SET_CAPTUREPOLARITY(&ic_handle, TIM_CHANNEL_2, TIM_ICPOLARITY_RISING); 这行代码是在使用STM32 HAL库(硬件抽象层库)进行定时器(TIM)的输入捕获配置时使用的。

代码流程:定义一个结构体,用于存放标志位和统计时间,在回调函数中,当用户按下按键,最初赋值是下降沿检测,检测到下降沿触发中断,响应回调函数,在回调函数中,初始的相应完整按键的标志位为0,执行if,初始响应上升沿标志位为0,执行else,在else中,将响应上升沿标志位 置1,并且将检测下降沿重置为检测上升沿,则在下次进入回调函数时,由于检测完整按键的标志位依旧为0,进入if,检测上升沿标志位为1,进入if,执行上升沿函数代码段。

代码如下:

ic.c

cpp 复制代码
#include "ic.h"
#include "stdio.h"
#include "string.h"

struct
{
    uint8_t succeed_flag;//完整的按键动作标志位
    uint8_t rising_flag;//上升标志位
    uint8_t falling_flag;//下降标志位
    uint16_t timeout_cnt;//时间统计
    
}capture_status={0};//全部初始化为0

TIM_HandleTypeDef ic_handle = {0};

//初始化输入捕获函数
void ic_init(uint16_t psc,uint16_t arr)
{
    TIM_IC_InitTypeDef ic_config = {0};
    ic_handle.Instance = TIM2;//定时器2
    ic_handle.Init.Prescaler = psc;
    ic_handle.Init.Period = arr;
    ic_handle.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
    HAL_TIM_IC_Init(&ic_handle);
    
    ic_config.ICFilter = 0;//过滤器0,也就是不要过滤器
    ic_config.ICPolarity = TIM_ICPOLARITY_FALLING;//下降沿捕获
    ic_config.ICPrescaler = TIM_ICPSC_DIV1;//每次在捕获输入上检测到边缘时执行捕获
    ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;//输入捕获触发信号源直接连接到对应通道的输入捕获引脚,这意味着输入捕获触发信号直接作用于定时器的输入捕获电路,不需要通过其他中间寄存器或外部电
    HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_config,TIM_CHANNEL_2);
    
    __HAL_TIM_ENABLE_IT(&ic_handle,TIM_IT_UPDATE);//更新中断
    HAL_TIM_IC_Start_IT(&ic_handle,TIM_CHANNEL_2);
    
}

//初始化MSP函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        GPIO_InitTypeDef gpio_initstruct;
        
        __HAL_RCC_TIM2_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        gpio_initstruct.Mode = GPIO_MODE_INPUT;//复式推挽输出
        gpio_initstruct.Pin = GPIO_PIN_1;//引脚1
        gpio_initstruct.Pull = GPIO_PULLUP;//上拉输出
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;//高速
        
        HAL_GPIO_Init(GPIOA,&gpio_initstruct);
        
        HAL_NVIC_SetPriority(TIM2_IRQn,2,2);//外部中断号,抢占优先级/响应优先级
        HAL_NVIC_EnableIRQ(TIM2_IRQn);
    }
    
}

//中断服务函数
void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&ic_handle);
}

//回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        if(capture_status.succeed_flag == 0)//完整按键捕获标志位,等于0代表不完整
        {
            if(capture_status.rising_flag == 1)//上升沿按键捕获标志,等于1代表捕获到上升沿
            {
                printf("捕获一个上升沿\r\n");
                memset(&capture_status,0,sizeof(capture_status));//对一整个结构体的清零
                TIM_RESET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2);//重置捕获通道2
                TIM_SET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_FALLING);//配置捕获为下升沿
            
            }
            else
            {
                //未捕获到上升沿,现在是下降沿
                printf("捕获一个下降沿\r\n");
                memset(&capture_status,0,sizeof(capture_status));//对一整个结构体的清零
                capture_status.rising_flag = 1;//置位1,表示接下来的边沿检测是上升沿的
                TIM_RESET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2);//重置捕获通道2
                TIM_SET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_RISING);//配置捕获为上升沿
            }
        }
    }
}

打开串口助手

按下按键2,串口显示

5、测量按键按下时间

回顾:如何测量按键按下的时间计算?

在按键按下的瞬间,检测到下降沿时,立刻把计时器关闭,将计数器置0,再打开(重启一下),使其从头开始计数,在未检测到上升沿之前,计数器中间可能会发生几次溢出,当检测到上升沿时,关闭计数器,查询当前计数器寄存器中的值。

复制项目文件21-捕获一次完整的按键动作,重命名为22-测量按键按下时间

__HAL_TIM_DISABLE(&ic_handle); 这行代码是在使用STM32 HAL库时,用于禁用(或停止)一个定时器的功能
__HAL_TIM_SET_COUNTER(&ic_handle, 0); 这行代码在使用STM32 HAL库时,用于设置定时器的计数器值。
__HAL_TIM_ENABLE(&ic_handle);//用于打开计数器
HAL_TIM_ReadCapturedValue(&ic_handle, TIM_CHANNEL_2); 这行代码在使用STM32 HAL库时,用于读取定时器指定通道的捕获值。

代码如下:

main.c

cpp 复制代码
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "ic.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    led_init();//初始化led灯
    uart1_init(115200);
    //printf("hello word!\r\n");
    ic_init(72-1,65536-1);

    while(1)
    { 
        pressed_time_get();
        delay_ms(500);
    }
}

ic.c

cpp 复制代码
#include "ic.h"
#include "stdio.h"
#include "string.h"

struct
{
    uint8_t succeed_flag;//完整的按键动作标志位
    uint8_t rising_flag;//上升标志位
    uint8_t falling_flag;//下降标志位
    uint16_t timeout_cnt;//时间统计
    
}capture_status={0};//全部初始化为0
uint16_t last_cnt = 0;

TIM_HandleTypeDef ic_handle = {0};

//初始化输入捕获函数
void ic_init(uint16_t psc,uint16_t arr)
{
    TIM_IC_InitTypeDef ic_config = {0};
    ic_handle.Instance = TIM2;//定时器2
    ic_handle.Init.Prescaler = psc;
    ic_handle.Init.Period = arr;
    ic_handle.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数模式
    HAL_TIM_IC_Init(&ic_handle);
    
    ic_config.ICFilter = 0;//过滤器0,也就是不要过滤器
    ic_config.ICPolarity = TIM_ICPOLARITY_FALLING;//下降沿捕获
    ic_config.ICPrescaler = TIM_ICPSC_DIV1;//每次在捕获输入上检测到边缘时执行捕获
    ic_config.ICSelection = TIM_ICSELECTION_DIRECTTI;//输入捕获触发信号源直接连接到对应通道的输入捕获引脚,这意味着输入捕获触发信号直接作用于定时器的输入捕获电路,不需要通过其他中间寄存器或外部电
    HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_config,TIM_CHANNEL_2);
    
    __HAL_TIM_ENABLE_IT(&ic_handle,TIM_IT_UPDATE);//更新中断
    HAL_TIM_IC_Start_IT(&ic_handle,TIM_CHANNEL_2);
    
}

//初始化MSP函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        GPIO_InitTypeDef gpio_initstruct;
        
        __HAL_RCC_TIM2_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        gpio_initstruct.Mode = GPIO_MODE_INPUT;//复式推挽输出
        gpio_initstruct.Pin = GPIO_PIN_1;//引脚1
        gpio_initstruct.Pull = GPIO_PULLUP;//上拉输出
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;//高速
        
        HAL_GPIO_Init(GPIOA,&gpio_initstruct);
        
        HAL_NVIC_SetPriority(TIM2_IRQn,2,2);//外部中断号,抢占优先级/响应优先级
        HAL_NVIC_EnableIRQ(TIM2_IRQn);
    }
    
}

//中断服务函数
void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&ic_handle);
}

//回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        if(capture_status.succeed_flag == 0)//完整按键捕获标志位,等于0代表不完整
        {
            if(capture_status.rising_flag == 1)//上升沿按键捕获标志,等于1代表捕获到上升沿
            {
                printf("捕获一个上升沿\r\n");
                capture_status.succeed_flag = 1;//检测到一个完整的按键动作
                
                TIM_RESET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2);//重置捕获通道2
                TIM_SET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_FALLING);//配置捕获为下升沿
                last_cnt = HAL_TIM_ReadCapturedValue(&ic_handle, TIM_CHANNEL_2);//用于读取定时器指定通道的捕获值。
            }
            else
            {
                //未捕获到上升沿,现在是下降沿
                printf("捕获一个下降沿\r\n");
                memset(&capture_status,0,sizeof(capture_status));//对一整个结构体的清零
                capture_status.rising_flag = 1;//置位1,表示接下来的边沿检测是上升沿的
                __HAL_TIM_DISABLE(&ic_handle); //用于禁用(或停止)一个定时器的功能
                __HAL_TIM_SET_COUNTER(&ic_handle, 0);//用于设置定时器的计数器值。
                TIM_RESET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2);//重置捕获通道2
                TIM_SET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_RISING);//配置捕获为上升沿
                __HAL_TIM_ENABLE(&ic_handle);//用于打开计数器
            }
        }
    }
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//溢出中断回调函数
{
    if(htim->Instance == TIM2)
    {
       if(capture_status.succeed_flag == 0)//在没有产生完整按键时
       {
           if(capture_status.rising_flag == 1)//边沿检测标志位等于1,代表在产生下降沿之后,上升沿之前这段时间,产生的溢出进行计数
           {
               capture_status.timeout_cnt++;
           }
       }
    }
}
void pressed_time_get(void)
{
    if(capture_status.succeed_flag == 1)//产生完整按键之后,执行下面代码,未产生完整按键,不执行下面代码
    {
        printf("按下时间为:%d \r\n",capture_status.timeout_cnt * 65536 + last_cnt);
        memset(&capture_status,0,sizeof(capture_status));//对一整个结构体的清零
    }
}

ic.h

cpp 复制代码
#ifndef __IC_H__
#define __IC_H__
#include "sys.h"

void ic_init(uint16_t psc,uint16_t arr);
void pressed_time_get(void);

#endif

打开串口助手

按下KEY2

可计算按键按下的时间

相关推荐
大筒木老辈子3 小时前
Linux笔记---协议定制与序列化/反序列化
网络·笔记
草莓熊Lotso3 小时前
【C++】递归与迭代:两种编程范式的对比与实践
c语言·开发语言·c++·经验分享·笔记·其他
我爱挣钱我也要早睡!6 小时前
Java 复习笔记
java·开发语言·笔记
汇能感知11 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun11 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao11 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾12 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT12 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
ST.J13 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin13 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全