基于STM32系统定时器(SysTick)非阻塞按键编程方法

前言:本思路方法,参考了B站UP主江协科技,的非阻塞按键的写法。在此基础上采用SysTick系统定时器实现

一、基础知识

1.1 SysTick

1.1.1 SysTick 简介

​ SysTick—系统定时器是属于CM3 内核中的一个外设,内嵌在NVIC 中。系统定时器是一个24bit的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK,一般我们设置系统时钟SYSCLK等于72M。当重装载数值寄存器的值递减到0 的时候,系统定时器就产生一次中断,以此循环往复。
​ 因为SysTick 是属于CM3 内核的外设,所以所有基于CM3 内核的单片机都具有这个系统定时器,使得软件CM3 单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。

1.1.2 SysTick 寄存器介绍

​ SysTick—系统定时器有4 个寄存器,简要介绍如下。在使用SysTick 产生定时的时候,只需要配置前三个寄存器,最后一个校准寄存器不需要使用,今天我们只使用最小时基,直接调用函数即可,以下寄存器了解即可。

寄存器名称寄存器描述
CTRLSysTick控制及状态寄存器
LOADSysTick重装载数值寄存器
VALSysTick当前数值寄存器
CALIBSysTick校准数值寄存器

1.2 按键

1.2.1 按键抖动

​ 按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。

​ 按键消抖,一般分为硬件消抖和软件消抖,硬件消抖分为,采用电容滤波消抖和采用RS触发器消抖,好处是电路稳定,缺点是由于需要硬件介入,会增加成本,所以使用场合,很少。用途更广的是采用软件消抖。

1.2.2 软件消抖

Delay 延时函数按键消抖(采用阻塞方式)

好处:结构简单,方便理解和编写

坏处:过度的消耗CPU资源,时间不够准确

int8_t KEY_EX_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    int8_t key=0;
    if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)==KEY_EX_ON)
    {
        Delay_ms(20);
        while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)==KEY_EX_ON);
        Delay_ms(20);
        key=1;
    }
  return key;
}

​ 分析函数,知道程序会很长时间阻塞在Delay_ms(20),这个延时函数,如果此时,主函数需要一个灯不停闪烁时,当按键按下后,大概几率LED灯将停止闪烁,此时程序发生阻塞,CPU无法执行LED灯闪烁程序。所以在实时性要求很高的场合或者需要多个设备同时响应时,这样的写法将不在适用。

定时器按键消抖(非阻塞方式)

原理:使用定时器,代替原来的Delay_ms(20),完成延时的目的,具体过程,分为三步

第一:初始化定时器,确实最小时基

第二:编写按键处理函数,按键检测判断逻辑函数

第三:设立按键标志位,进行20ms定时分频操作,编写中断处理函数

[!CAUTION]

注意:逻辑说明,按键如果本次是高电平,上次是低电平,就表明按键已经按下,且目前是松手状态,返回按键码值,并复位标志位。

二、具体程序编写

2.1 初始化 SysTick定时器

编写SysTick.c、SysTick.h文件

SysTick.c、SysTick.h

#include "stm32f10x.h"
#include "SysTick.h"
void SysTick_Init(void)
{
    /* SystemFrequency / 1000 1ms 中断一次
    9 * SystemFrequency / 100000 10us 中断一次
    10 * SystemFrequency / 1000000 1us 中断一次
    */
    if(SysTick_Config(SystemCoreClock/1000))
    {
        while(1);
    }
}
#ifndef SYSTICK_H
#define SYSTICK_H


void SysTick_Init(void);
#endif //SYSTICK_DELAY_H

SysTick_Init(),函数确定了,中断的最小时基,1ms中断一次,为后面分频做准备。

2.2 编写按键处理函数

编写app_key.c、app_key.h文件,在app_key.h文件,定义按键结构体变量和枚举变量

app_key.h

#ifndef APP_KEY_H
#define APP_KEY_H
#include "stm32f10x.h"

typedef enum
{
    KEY_Flag_OFF=0
    ,KEY_Flag_ON
 }KEYMode;//定义按键模式

typedef struct
{
    uint32_t KEY_Task_Cycle;    //按键扫描周期
    uint32_t KEY_Task_Time;		//按键消抖时间	
    KEYMode KEY_Task_Flag;		//按键标志位,这里数据类型采用的上面的定义的枚举结构体,不理解的可以分开写
}KEY_Info;
extern KEY_Info KEY1_info;//全局变量
extern KEY_Info KEY2_info;


//函数声明
void KEY_Task_Init(uint32_t Light_LED_Task_Cycles); //按键初始化,消抖扫描周期			
uint16_t KEY_GetNum_Task(void);						//按键检测,返回键值					
uint16_t KEY_GetState(void);						////获取按键,键值
#endif //APP_KEY_H

app_key.c

#include "app_key.h"
#include "key/bsp_key.h"
#include "key/bsp_key_EX.h"
KEY_Info KEY1_info;//结构体全局变量
KEY_Info KEY2_info;

/**
  * @brief  按键检测任务功能初始化函数
  * @param KEY_Task_Cycles   任务周期,20ms轮询一次
  * @note   无
  * @retval 无
  */
void KEY_Task_Init(uint32_t KEY_Task_Cycles)
{
    KEY1_info.KEY_Task_Cycle = KEY_Task_Cycles;  //任务周期
    KEY1_info.KEY_Task_Flag=KEY_Flag_OFF;		//清除标志位
}

/**
  * @brief  按键检测任务函数
  * @param  无
  * @note   无
  * @retval 返回当前键值
  */
uint16_t KEY_GetNum_Task(void)
{
    static  uint16_t Key_CurrState,Key_PervState;
    //*** 重要内容,定义两个静态变量,分别存贮,按键的本次值,和上次值
    if(KEY1_info.KEY_Task_Flag==KEY_Flag_ON)
    {
        Key_PervState=Key_CurrState;  //首次时,先将本次值赋给上次值,上一次,就是上一次的本次
        Key_CurrState=KEY_GetState(); //获取本次值
        if(Key_CurrState==0&&Key_PervState!=0) //判断本次值为0,而上次值非0,时表示按键按下
        {
            KEY1_info.KEY_Task_Flag=KEY_Flag_OFF;//清除标志位
            return Key_PervState;       	//返回当前键值
        }
    }
    return 0;
}
/**
  * @brief  按键处理函数
  * @param  无
  * @note   无
  * @retval 返回当前编码后的按键,键值
  */
uint16_t KEY_GetState(void)
{

    if(GPIO_ReadInputDataBit(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==KEY_EX_ON)
    {
        return 1; //按键1,按下,返回编码1
    }
    if(GPIO_ReadInputDataBit(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==KEY_EX_ON)
    {
        return 2;//按键2,按下,返回编码2
    }
    return 0;//否则返编码0
}

[!CAUTION]

在实际使用按键检测时,只需要调用,void KEY_Task_Init(uint32_t KEY_Task_Cycles)和uint16_t KEY_GetNum_Task(void),这两个函数。这样做到了函数的高度封装和低耦合

static uint16_t Key_CurrState,Key_PervState,这两个变量一定要是,静态变量,这样退出函数后,数据才不会丢失

2.3 编写中断处理函数

stm32f10x_it.c 文件中,找到void SysTick_Handler(void),此函数为该.c文件自带函数

#include "app_key.h"
void SysTick_Handler(void)
{
  if(KEY1_info.KEY_Task_Time==KEY1_info.KEY_Task_Cycle) //时间计数到达指定周期后,标志位置位,时间清零
  {
      KEY1_info.KEY_Task_Flag=KEY_Flag_ON;
      KEY1_info.KEY_Task_Time=0;
  }
  else
  {
    KEY1_info.KEY_Task_Time++;
  }
   //其他情况,每中断一次时间自增一
}

[!TIP]

也可以通过,将按键逻辑,写在中断里面,但是中断要求的是快进快出,所以尽可能的少最好,切记中断里面不要有普通的延时函数,否则中断处理函数,将堵塞。

三、总结

采用Systick系统定时器,产生最小时基的方法,来进行按键消抖检测,极大的提高了按键的准确性和实时性。避免了程序造成堵塞的弊端。极大的提高了,cpu的利用率,下面用一个例子,来展现这种按键的好处。

例子:功能1板载LED绿灯,以50ms为周期,心跳闪烁,循环执行,功能2外部按键KEY1,控制板载蓝灯的亮灭,要求,在这个过程不能干扰到心跳灯运行

主函数

#include "main.h"
#include "stm32f10x.h"
#include "NTC/bsp_ntc.h"
/**
  * @brief  主函数
  * @param  无 
  * @note   无
  * @retval 无
  */
int main(void)
{
 	uint16_t keynum,count1=0
    LED_GPIO_config();
    KEY_GPIO_config();
    SysTick_Init();
    Heartbeat1_LED_Task_Init(100);
    KEY_Task_Init(20);//按键周期设置为20ms
   while (1)
    {
      Heartbeat1_LED_Task();
      keynum=KEY_GetNum_Task();
      if(keynum==1)
      {
        B_LED_TOGGLE;//取反
      }
    }
}

led.c、key.c

/**
******************************************************************************
      * @file        app_led.c
      * @author      huangwei
      * @version     V1.0
      * @date        2024年11月6日
      * @brief       心跳灯1应用层功能接口
******************************************************************************
**/
#include "app_led.h"
#include "LED/bsp_led.h"

uint16_t Heartbeat1_Led_Task_Flag;
uint32_t Heartbeat1_Led_Task_Cycle;
uint32_t Heartbeat1_Led_Task_Time;



/**
  * @brief  心跳灯任务函数
  * @param  无
  * @note   无
  * @retval 无
  */
void Heartbeat1_LED_Task()
{
    if(Heartbeat1_Led_Task_Flag==1)
    {
        G_LED_TOGGLE;
        //Heartbeat1_LED_Task_Rest();
        Heartbeat1_Led_Task_Flag=0;
    }
}

/**
  * @brief  心跳灯任务计数复位函数
  * @param  无
  * @note   无
  * @retval 无
  */
void Heartbeat1_LED_Task_Rest()
{
    Heartbeat1_Led_Task_Time=Heartbeat1_Led_Task_Cycle;
    Heartbeat1_Led_Task_Flag=0;
}
/**
  * @brief  心跳灯任务初始化函数
  * @param Heartbeat1_LED_Cycles: 任务周期 单位ms(可修改系统节拍定时器)
  * @note   无
  * @retval 无
  */
void Heartbeat1_LED_Task_Init(uint32_t Heartbeat1_LED_Cycles)
{
    Heartbeat1_Led_Task_Cycle=Heartbeat1_LED_Cycles;
    //Heartbeat1_LED_Task_Rest();
    Heartbeat1_Led_Task_Flag=0;
}
/**
******************************************************************************
      * @file        app_key.c
      * @author      huangwei
      * @version     V1.0
      * @date        2024年11月6日
      * @brief       按键应用层功能接口
******************************************************************************
**/
#include "app_key.h"
#include "key/bsp_key.h"
#include "key/bsp_key_EX.h"
KEY_Info KEY1_info;//结构体全局变量
KEY_Info KEY2_info;

/**
  * @brief  按键模块任务功能初始化函数
  * @param  KEY_Task_Cycles   任务周期,100ms轮训一次
  * @note   无
  * @retval 无
  */
void KEY_Task_Init(uint32_t KEY_Task_Cycles)
{
    KEY1_info.KEY_Task_Cycle = KEY_Task_Cycles;
    //KEY_Task_Rest();
    KEY1_info.KEY_Task_Flag=KEY_Flag_OFF;
}

/**
  * @brief  按键任务计数复位函数
  * @param  无
  * @note   无
  * @retval 无
  */
  
void KEY_Task_Rest(void)
{
    KEY1_info.KEY_Task_Time=KEY1_info.KEY_Task_Cycle;
    KEY1_info.KEY_Task_Flag=KEY_Flag_OFF;
}

/**
  * @brief  按键任务函数
  * @param  无
  * @note   无
  * @retval 返回当前按键值
  */
uint16_t KEY_GetNum_Task(void)
{
    static  uint16_t Key_CurrState,Key_PervState;
    if(KEY1_info.KEY_Task_Flag==KEY_Flag_ON)
    {
        Key_PervState=Key_CurrState;
        Key_CurrState=KEY_GetState();
        if(Key_CurrState==0&&Key_PervState!=0)
        {

            KEY1_info.KEY_Task_Flag=KEY_Flag_OFF;
            return Key_PervState;
        }
        //KEY_Task_Rest();
    }
    return 0;
}
/**
  * @brief  按处理键编码函数
  * @param  无
  * @note   无
  * @retval 返回按键编码值
  */
uint16_t KEY_GetState(void)
{

    if(GPIO_ReadInputDataBit(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==KEY_EX_ON)
    {
        return 1;
    }
    if(GPIO_ReadInputDataBit(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==KEY_EX_ON)
    {
        return 2;
    }
    return 0;
}

Systick 中断处理函数

void SysTick_Handler(void)
{
  if(Heartbeat1_Led_Task_Time==Heartbeat1_Led_Task_Cycle)
  {
       Heartbeat1_Led_Task_Flag=1;
       Heartbeat1_Led_Task_Time=0;
  }
  else
  {
    Heartbeat1_Led_Task_Time++;
    //Heartbeat1_Led_Task_Flag=0;
  }

  if(KEY1_info.KEY_Task_Time==KEY1_info.KEY_Task_Cycle)
  {
      KEY1_info.KEY_Task_Flag=KEY_Flag_ON;
      KEY1_info.KEY_Task_Time=0;
  }
  else
  {
    KEY1_info.KEY_Task_Time++;
    //KEY1_info.KEY_Task_Flag=KEY_Flag_OFF;
  }

}

程序现象
在这里插入图片描述可以看出,按键并没有影响到心跳灯,表明此方法完成了,按键的非阻塞检测扫描,并且将按键周期设置,进行打包封装,展现出了很高的灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值