单片机按键扫描程序,可以单击、双击、长按,使用状态机,无延时,不阻塞。

news2025/1/7 13:37:50
  • 根据按下时的时长、间隔来判断是否是连按或者长按。
  • 当连按间隔很短时,计录连按次数
  • 超过连接间隔时,回报按下次数
  • 根据按键次数自行判断是单击、双击、三击、四击。。。最多记录15击。

结构体版:

#define KEY_CHANNEL_COUNT (6 + 8 + 8)
struct keyInfo
{
    uint8_t act : 4;       // 按了多少次,最多连按15次
    uint8_t down : 1;      // 按下了
    uint8_t up : 1;        // 松开了
    uint8_t longPress : 1; // 长按了
    uint8_t io : 1;        // 按键IO状态
    uint8_t intervalTime;  // 连按间隔时间
    uint8_t holdTime;      // 长按时间
    uint8_t duration;      // 按键次数保持时间,超过后,act清零
};
struct keyInfo keyValues[KEY_CHANNEL_COUNT] = {0};

void button_trace_handle(void *p)
{
    static uint32_t lastTime = 0;
    const uint8_t KEYSCAN_INTERVAL_TIME = 10; // 按键扫描间隔时间
    const uint8_t LONG_PRESS_TIME = 100;  // 长按多久生效, 实际时间为,下面同理 LONG_PRESS_TIME * KEYSCAN_INTERVAL_TIME
    const uint8_t INTERVAL_TIME_SET = 20; // 两次按键检测超时
    const uint8_t ANTI_SHAKE_TIME = 2;    // 按键防抖检测超时

    /**
     * @brief 需要实现millis()函数,系统毫秒计时器。
     * 
     */
    if (millis()> lastTime + KEYSCAN_INTERVAL_TIME)
    {
        lastTime = millis();        
    }
    else
    {
        return;
    }

    /**给按键IO赋值, 有多个按键就传多少个, 自己实现ic_read函数 */
    for (uint8_t i = 0; i < KEY_CHANNEL_COUNT; i++)
    {
        keyValues[i].io = io_read(i);
    }
    

    for (uint8_t i = 0; i < KEY_CHANNEL_COUNT; i++)
    {
        if (keyValues[i].io)//按下了
        {
            if (keyValues[i].holdTime < LONG_PRESS_TIME)
            {
                keyValues[i].holdTime++;
                if (keyValues[i].holdTime >= ANTI_SHAKE_TIME)//防抖
                {
                    keyValues[i].down = 1;
                    keyValues[i].intervalTime = INTERVAL_TIME_SET;
                }
            }
            else //长按了,会一直标记,直到松开
            {
                keyValues[i].longPress = 1;
            }
        }
        else
        {
            //松开了
            keyValues[i].holdTime = 0;
            keyValues[i].longPress = 0;
            if (keyValues[i].down)//按下过了
            {
                if (!keyValues[i].longPress)//不是长按
                    keyValues[i].act++;//按下次数+1
                keyValues[i].down = 0;
            }
            if (keyValues[i].intervalTime)//连按超时
            {
                keyValues[i].intervalTime--;
                if (keyValues[i].intervalTime == 1)
                {
                    LOG_D("key[%d] act:%d", i, keyValues[i].act);//打印哪个按键按了多少次
                    keyValues[i].duration = 10;
                }
            }
        }

        if (keyValues[i].duration >= 1)
        {
            keyValues[i].duration--;
            if (keyValues[i].duration == 1)//按键次数保持时间到
            {
                keyValues[i].act = 0;
            }
        }
    }
}

无结构体版,更方便移到51单片机上

#define KEY_DOWN_MASK  0X80/**按下标记*/
#define KEY_LONG_PRESS_MASK  0X40/**长按标记 */
#define KEY_TIMEOUT_MASK  0X10/**超时标志,此时返回按键值*/
#define KEY_TIEMES_MASK  0X0F/**按了多少次 */
#define KEY_VALUE(x) (0x0001<<(x))

#define KEY_COUNTS 5

void keyScanPro()
{

    const uint8_t SHORT_PRESS_TIME = 25;
    const uint16_t LONG_PRESS_TIME = 150;
    const uint8_t IS_KEY_DOWN = 0X80;/**按下了 */
    const uint8_t IS_LONG_PRESS = 0X40;/**长按了 */
    const uint8_t IS_TIME_OUT = 0X10;/**退好久没按 */
    static uint8_t keyActionHold = 0;
    static uint8_t pressTimesRecord[KEY_COUNTS] = { 0 };
    static uint8_t pressTime[KEY_COUNTS] = { 0 };
    static uint16_t longPressTime[KEY_COUNTS] = { 0 };
    const uint16_t channel_keyScan_map[KEY_COUNTS] = { DEF_SET_BIT0,DEF_SET_BIT1,DEF_SET_BIT2,DEF_SET_BIT3,DEF_SET_BIT4 };/**A,B,C,D,E,F,G,H,I,J,K,L对就的键值*/

    uint8_t i;

    keyValue = KP; keyValue <<= 1;
    keyValue |= !K1; keyValue <<= 1;
    keyValue |= !K2; keyValue <<= 1;
    keyValue |= !K3; keyValue <<= 1;
    keyValue |= !K4;

    if (keyValue != keyValuePre)
    {
        ResetSystemShutdownCountdown();
        keyValuePre = keyValue;
    }
    // LOG("keyValue:%d\n",(int)keyValue);
    if (keyAction)
    {
        if (keyActionHold++ > 100)
        {
            keyActionHold = 0;
            keyAction = 0;
        }
    }

    for (i = 0; i < KEY_COUNTS; i++)
    {
        if (keyValue & channel_keyScan_map[i])
        {
            //按下了
            if (longPressTime[i] < LONG_PRESS_TIME)
            {
                longPressTime[i]++;
                pressTime[i] = SHORT_PRESS_TIME;
                pressTimesRecord[i] |= IS_KEY_DOWN;

            }
            else
            {
                pressTimesRecord[i] |= IS_LONG_PRESS;
                pressTimesRecord[i] |= IS_TIME_OUT;
                pressTimesRecord[i] &= ~IS_KEY_DOWN; //取消标记高位
                keyLongPress |= 1 << i;
                keyAction |= ((i + 1) << 8);
                if (keyValue & PWR_KEY_VALUE)
                {
                    LOG("System shutting down ...");
                    SYS_PWR_SHUTDOWN();
                    while (1);

                }
                LOG("long press:%d\n", (int)keyLongPress);
                // if (pwrKeyLongPressCb) pwrKeyLongPressCb();
                // else pwrKeyLongPressCbDefault();
            }

        }
        else
        {
            //松开了
            longPressTime[i] = 0;
            keyLongPress &= ~(1 << i);
            if (pressTimesRecord[i] & IS_KEY_DOWN)
            {
                //高位标记过,即按下过
                keyActionHold = 0;
                pressTimesRecord[i] &= ~IS_KEY_DOWN; //取消标记高位
                if ((pressTimesRecord[i] & KEY_TIEMES_MASK) < 15)
                {
                    uint8_t ptc = 0;
                    pressTimesRecord[i]++;
                    ptc = pressTimesRecord[i] & KEY_TIEMES_MASK;
                    ptc = ptc > 7 ? 7 : ptc;
                    // speaker_out(music_note_freq[ptc], 100);
                }

            }
            if (pressTime[i] > 0)
            {
                if (pressTime[i] == 1)
                {
                    pressTimesRecord[i] |= IS_TIME_OUT;//BIT4 为检测时间到
                }
                pressTime[i]--;
            }
            if (pressTimesRecord[i] & IS_TIME_OUT)
            {

                if (pressTimesRecord[i] & IS_LONG_PRESS)
                {
                    // rt_kprintf("Long press:%d \n", i);
                    pressTimesRecord[i] &= ~IS_LONG_PRESS;


                }
                else if (pressTimesRecord[i] & KEY_TIEMES_MASK)
                {
                    uint8_t ptc = pressTimesRecord[i] & KEY_TIEMES_MASK;
                    keyAction |= (i + 1) << 4 | ptc;
                    LOG("keyAction:%x\n", (int)keyAction);

                    // struct pwrKeyActList* p;
                    // p = &pwrKeyActListHead;
                    // do {
                    //     // LOG_D("P:0x%08X", p);
                    //     if (p->cb)
                    //     {
                    //         p->cb(ptc);
                    //     }
                    //     p = p->next;
                    // } while (p);
                }
                // LOG("Press:%d - %d\n", (int)i, (int)(pressTimesRecord[i] & KEY_TIEMES_MASK));;
                pressTimesRecord[i] = 0;
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2271755.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机场安全项目|基于改进 YOLOv8 的机场飞鸟实时目标检测方法

目录 论文信息 背景 摘要 YOLOv8模型结构 模型改进 FFC3 模块 CSPPF 模块 数据集增强策略 实验结果 消融实验 对比实验 结论 论文信息 《科学技术与工程》2024年第24卷第32期刊载了中国民用航空飞行学院空中交通管理学院孔建国, 张向伟, 赵志伟, 梁海军的论文——…

《Rust权威指南》学习笔记(二)

枚举enum 1.枚举的定义和使用如下图所示&#xff1a; 定义时还可以给枚举的成员指定数据类型&#xff0c;例如&#xff1a;enum IpAddr{V4(u8, u8, u8, u8),V6(String),}。枚举的变体都位于标识符的命名空间下&#xff0c;使用::进行分隔。 2.一个特殊的枚举Option&#xff0…

OSI模型的网络层中产生拥塞的主要原因?

&#xff08; 1 &#xff09;缓冲区容量有限&#xff1b;&#xff08; 1.5 分&#xff09; &#xff08; 2 &#xff09;传输线路的带宽有限&#xff1b;&#xff08; 1.5 分&#xff09; &#xff08; 3 &#xff09;网络结点的处理能力有限&#xff1b;&#xff08; 1 分…

linux上安装MySQL教程

1.准备好MySQL压缩包&#xff0c;并进行解压 tar -xvf mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar -C /usr/local 2.检查是否有mariadb数据库 rpm -aq|grep mariadb 关于mariadb:是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可 MariaDB的目…

R语言中的时间序列分析·

1 数据集说明 AirPassengers 1949~1960年每月乘坐飞机的乘客数 JohnsonJohnson Johnson&Johnson每股季度收入 nhtemp 康涅狄格州纽黑文地区从1912年至1971年每年的平均气温 Nile 尼罗河的流量 sunspots 1749年~1983年月平均太阳黑子数 2 相关包 xts、forecast、tser…

LookingGlass使用

背景 Looking Glass 是一款开源应用程序&#xff0c;可以直接使用显卡直通的windows虚拟机。 常见环境是Linux hostwindows guest&#xff0c;基本部署结构图&#xff1a; 编译 git clone --recursive https://github.com/gnif/LookingGlass.git编译client mkdir client/b…

HCIA-Access V2.5_7_3_XG(S)原理_关键技术

为什么需要测距 因为上行链路只有一根纤,而且每一个ONU到OLT的距离是不一样的,虽然上行通过TDMA技术,让每一个ONU在不同的时间段发送数据,但是仍然有可能在同一时刻到达分光器,产生数据冲突。 有测距的信元传输 所以为了避免碰撞冲突,通过ONU在注册的时候就会启动测距…

四、VSCODE 使用GIT插件

VSCODE 使用GIT插件 一下载git插件与git Graph插件二、git插件使用三、文件提交到远程仓库四、git Graph插件 一下载git插件与git Graph插件 二、git插件使用 git插件一般VSCode自带了git&#xff0c;就是左边栏目的图标 在下载git软件后vscode的git插件会自动识别当前项目 …

RISC-V学习笔记

1.RISC ISA1个基本整数指令集多个可选的扩展指令集&#xff0c;如RV32I表示支持32位整数指令集。I表示基本指令集&#xff0c;M表示整数乘法与除法指令集&#xff0c;A表示存储器原子指令集&#xff0c;F表示单精度浮点指令集&#xff0c;D表示双精度浮点指令集等&#xff0c;C…

strapi中使用Documentation插件

Swagger UI 自动生成并展示了 API 的文档&#xff0c;这些文档是根据 OpenAPI Specification (OAS) 格式编写的。它提供了对 API 端点、请求方法&#xff08;GET, POST, PUT, DELETE 等&#xff09;、参数、响应格式等详细信息的描述 安装 npm run strapi install documentat…

AI来帮忙:蛋白纯化不用慌

在当今生物学研究的前沿领域&#xff0c;从探索疾病的发病机制&#xff0c;到新型药物的研发&#xff0c;再到生物工程产品的制造&#xff0c;高纯度、高活性的蛋白质都是不可或缺的基石。 科研人员在蛋白纯化的征程中&#xff0c;时常被诸多难题困扰。一方面&#xff0c;生物…

SpringCloud系列教程:微服务的未来(六)docker教程快速入门、常用命令

对于开发人员和运维工程师而言&#xff0c;掌握 Docker 的基本概念和常用命令是必不可少的。本篇文章将带你快速入门 Docker&#xff0c;并介绍一些最常用的命令&#xff0c;帮助你更高效地进行开发、测试和部署。 目录 前言 快速入门 docker安装 配置镜像加速 部署Mysql …

基于单片机中药存放环境监测系统的实现

基于单片机中药存放环境监测系统的实现 项目开发背景 随着现代中药的广泛应用&#xff0c;中药材的存储环境对其质量有着至关重要的影响。温湿度、烟雾、火灾等环境因素&#xff0c;若不加以控制&#xff0c;将会导致中药材失效或变质。因此&#xff0c;设计一个基于单片机的…

casaos安装最新版homeassistant-arm

进入cosOS界面点自定义安装 Docker镜像:homeassistant/armv7-homeassistant Tag:2024.12.2 标题&#xff1a;Home Assistant 图片路径&#xff1a;https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStoremain/Apps/HomeAssistant/icon.png Web UI&#xff1a;http&…

Fabric环境部署-安装Go

安装go语言环境 国内镜像&#xff1a;Go下载 - Go语言中文网 - Golang中文社区 1.选择版本下载后解压&#xff1a;注意go1.11.linux-amd64.tar.gz换成你下的 sudo tar zxvf go1.21.linux-amd64.tar.gz -C /usr/local 2.. 创建Go目录 mkdir $HOME/go 3. 用vi打开~./bashrc&…

慧集通(DataLinkX)iPaaS集成平台-主数据映射管理(多系统间基础档案的映射)

主数据管理 主数据管理主要是解决不同业务系统之间历史数据不统一的问题&#xff0c;在该功能下主要分为三个模块分别为数据对象、应用系统、数据映射&#xff1b; 其中数据对象指的是我们的不同的对象&#xff0c;如&#xff1a;部门、人员、职级、科目、供应商等等&#xff…

Hoverfly 任意文件读取漏洞(CVE-2024-45388)

漏洞简介 Hoverfly 是一个为开发人员和测试人员提供的轻量级服务虚拟化/API模拟/API模拟工具。其 /api/v2/simulation​ 的 POST 处理程序允许用户从用户指定的文件内容中创建新的模拟视图。然而&#xff0c;这一功能可能被攻击者利用来读取 Hoverfly 服务器上的任意文件。尽管…

基于单片机的公交车报站系统设计

引言&#xff1a;单片机应用实践是电类相关专业一门必修的专业技术基础课&#xff0c;其教学目的就是为了使学生能深入了解模拟电路、数字电路、EDA 技术、传感器、单片机原理及其相关接口的综合应用技术&#xff0c;为此我们选了一个典型的实践题目- 公交车报站系统设计&#…

基于Java的超级玛丽游戏的设计与实现【源码+文档+部署讲解】

目 录 1、绪论 1.1背景以及现状 1.2 Java语言的特点 1.3 系统运行环境及开发软件&#xff1a; 1.4 可行性的分析 1.4.1 技术可行性 1.4.2 经济可行性 1.4.3 操作可行性 2、 需求分析 2.1 用户需求分析 2.2功能需求分析 2.3界面设计需求分析…

关于数组的一些应用--------数组作函数的返回值(斐波那契数列数列的实现)

数组在作为函数的返回值&#xff0c;一个很经典的例子就是获取斐波那契数列的前N项 代码思路&#xff1a; 设计思路 输入&#xff1a; 输入一个整数 n&#xff0c;表示要生成斐波那契数列的长度。 输出&#xff1a; 输出一个长度为 n 的整数数组&#xff0c;其中每个元素为斐…