当前位置: 首页 > news >正文

【小制作】米家模拟手指点击

fec3d5d6223f4ddfba7a8ad0b93e61af.jpg

代码功能解释

这段代码是一个基于Arduino平台的控制程序,主要功能包括:

  1. 初始化:设置引脚模式、初始化编码器、舵机和EEPROM。
  2. 按键检测:处理按钮的单击、双击和长按事件,并根据事件执行相应操作。
  3. 编码器更新:检测旋转编码器的状态,调整变量值并控制LED闪烁。
  4. 舵机控制:根据设定的角度和速度移动舵机。
  5. LED控制:根据条件交替闪烁两个LED。

详细解释

  1. 初始化

    • 设置引脚模式为输入或输出。
    • 初始化编码器、舵机和EEPROM。
  2. 主循环

    • 持续检测按键状态。
    • 更新编码器状态。
    • 控制舵机运动。
    • 控制LED闪烁。
  3. 按键检测

    • 检查每个按钮的状态。
    • 根据按键事件(长按、双击、单击)执行不同操作。
  4. 编码器更新

    • 检测编码器旋转方向。
    • 根据旋转方向增加或减少变量值。
    • 切换LED状态。
  5. 舵机控制

    • 根据设定的角度和速度移动舵机。
    • 正向和反向移动舵机。
  6. LED控制

    • 控制两个LED交替闪烁。
#include "Arduino.h"
#include <Bounce2.h>
#include <Servo.h>
#include <EEPROM.h>// 定义引脚
#define EC11_A PB_2     // 编码器A
#define EC11_B PA_7     // 编码器B
#define BUTTON_1 PA_4   // 按钮1
#define BUTTON_2 PA_6   // 按钮2
#define PWM_1 PB_0      // PWM控制
#define LED_0 PA_0      // LED0
#define LED_1 PA_1      // LED1// 定义常量
const unsigned long debounceTime = 50;
const unsigned long longPressTime = 1000;
const unsigned long doubleClickTime = 300;
const long led_interval = 300;
const int DEFAULT_START_ANGLE = 90;
const int DEFAULT_STEPS = 100;// 定义变量
uint8_t fast_num = 1;    // 运动速度
uint8_t fast_delta = 90; // 运动距离
uint8_t Count_step = 1;  // 运动速度调节步进
bool Duoji_run_Flag = false;
bool CounterChanged = false;
int pos = 0;
int variableA = 0;
int variableB = 0;
bool isVariableA = true;// 定义LED状态
bool led0State = LOW;
bool led1State = LOW;
unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;
bool led0Blinked = false;
bool led1Blinked = false;// 定义按键状态结构体
struct ButtonState
{int pin;int lastState;int currentState;unsigned long lastDebounceTime;unsigned long lastClickTime;bool isLongPress;bool isSingleClickHandled;
};// 初始化按键状态
ButtonState button1 = {BUTTON_1, HIGH, HIGH, 0, 0, false, false};
ButtonState button2 = {BUTTON_2, HIGH, HIGH, 0, 0, false, false};// 初始化Bounce对象
Bounce encoderPinAButton = Bounce();
Bounce encoderPinBButton = Bounce();// 初始化Servo对象
Servo myservo;// 宏定义调试输出
#define _DRV_TAG_ " line"
#define serial_dbg(fmt, args...)                                                         \do                                                                                     \{                                                                                      \if (1)                                                                               \{                                                                                    \Serial.printf("" _DRV_TAG_ "%d  [%s] " fmt " \n", __LINE__, __FUNCTION__, ##args); \}                                                                                    \} while (0)// 初始化函数
void setup()
{Serial.begin(115200);serial_dbg("Hello, Air001. \n");// 设置LED引脚为输出模式pinMode(LED_0, OUTPUT);pinMode(LED_1, OUTPUT);// 初始化LED状态为灭digitalWrite(LED_0, LOW);digitalWrite(LED_1, LOW);// 设置按键引脚为输入模式pinMode(BUTTON_1, INPUT);pinMode(BUTTON_2, INPUT);// 初始化编码器sys_RotaryInit();// 初始化PWM频率和分辨率pinMode(PWM_1, OUTPUT);myservo.attach(PWM_1, 500, 2500); // 修正脉冲宽度read_eeprom();//读数据
}// 主循环函数
void loop()
{checkButton();    // 检测按键encoder_update();  // 更新编码器状态duoji();          // 控制舵机led_blink_once(); // 控制LED闪烁
}
void blinkLEDsAlternatingTwice() {// 保存当前LED状态bool savedLed0State = led0State;bool savedLed1State = led1State;// 交替闪烁两次for (int i = 0; i < 2; i++) {digitalWrite(LED_0, HIGH);digitalWrite(LED_1, LOW);delay(led_interval);digitalWrite(LED_0, LOW);digitalWrite(LED_1, HIGH);delay(led_interval);}// 恢复LED状态digitalWrite(LED_0, savedLed0State);digitalWrite(LED_1, savedLed1State);
}
void write_eeprom() {// 写入EEPROMEEPROM.write(0, fast_num);EEPROM.write(1, fast_delta);// 写完EEPROM后两颗LED交替闪烁两次blinkLEDsAlternatingTwice();
}
void read_eeprom() {// 读取EEPROMfast_num = EEPROM.read(0);fast_delta = EEPROM.read(1);if (fast_num == 0 || fast_delta == 0) {fast_num = DEFAULT_START_ANGLE;fast_delta = DEFAULT_STEPS;write_eeprom();}serial_dbg("fast_num: %d, fast_delta: %d", fast_num, fast_delta);
}
// 初始化编码器
void sys_RotaryInit()
{pinMode(EC11_A, INPUT);pinMode(EC11_B, INPUT);pinMode(BUTTON_1, INPUT_PULLUP);encoderPinAButton.attach(EC11_A);encoderPinAButton.interval(5);encoderPinBButton.attach(EC11_B);encoderPinBButton.interval(5);
}// 检测按键状态
void checkButton()
{checkButtonState(button1);checkButtonState(button2);
}// 处理按键状态
void checkButtonState(ButtonState &button)
{int reading = digitalRead(button.pin); // 读取按钮状态// 去抖动处理if (reading != button.lastState){button.lastDebounceTime = millis();}if ((millis() - button.lastDebounceTime) > debounceTime){if (reading != button.currentState){button.currentState = reading;if (button.currentState == LOW){button.lastClickTime = millis();button.isLongPress = false;button.isSingleClickHandled = false; // 新增标志位}else{unsigned long pressDuration = millis() - button.lastClickTime;if (pressDuration > longPressTime){button.isLongPress = true;handleLongPress(button.pin);}else if (!button.isLongPress){// 检查是否为双击if ((millis() - button.lastClickTime) < doubleClickTime && !button.isSingleClickHandled){if (digitalRead(button.pin) == HIGH){handleDoubleClick(button.pin);button.isSingleClickHandled = true; // 标记双击已处理}}else if (!button.isSingleClickHandled){// 如果不是双击,则处理单击事件handleSingleClick(button.pin);button.isSingleClickHandled = true;}}}}}button.lastState = reading;
}// 处理长按事件
void handleLongPress(int pin)
{switch (pin){case BUTTON_1:serial_dbg("Button 1 Long Press");isVariableA = !isVariableA;if (isVariableA){digitalWrite(LED_1, LOW);led1State = LOW;serial_dbg("Switching to Variable A");}else{digitalWrite(LED_1, HIGH);led1State = HIGH;serial_dbg("Switching to Variable B");}break;case BUTTON_2:serial_dbg("Button 2 Long Press");Duoji_run_Flag = true;break;default:break;}
}// 处理双击事件
void handleDoubleClick(int pin)
{switch (pin){case BUTTON_1:serial_dbg("Button 1 Double Click");Duoji_run_Flag = true;break;case BUTTON_2:serial_dbg("Button 2 Double Click");break;default:break;}
}// 处理单击事件
void handleSingleClick(int pin)
{switch (pin){case BUTTON_1:write_eeprom();serial_dbg("Button 1 Single Click");break;case BUTTON_2:serial_dbg("Button 2 Single Click");Duoji_run_Flag = true;break;default:break;}
}// 更新编码器状态
void encoder_update()
{encoderPinAButton.update();encoderPinBButton.update();// 检测编码器旋转if (encoderPinAButton.fell()){if (encoderPinBButton.read() == HIGH){if (isVariableA){variableA=fast_num;variableA += Count_step; // 顺时针旋转fast_num = variableA;}else{variableB = fast_delta;variableB += Count_step; // 顺时针旋转fast_delta = variableB;}}else{if (isVariableA){variableA=fast_num;variableA -= Count_step; // 逆时针旋转fast_num = variableA;}else{variableB = fast_delta;variableB -= Count_step; // 逆时针旋转fast_delta = variableB;}}CounterChanged = true;if (isVariableA){serial_dbg("Variable A= %d ", variableA);}else{serial_dbg("Variable B= %d ", variableB);}led0Blinked = false;led_blink_once();}
}// 控制舵机
void duoji()
{if (Duoji_run_Flag){int DEFAULT_END_ANGLE = fast_delta + DEFAULT_START_ANGLE;Duoji_run_Flag = false;unsigned long startTime = millis();int totalTime = fast_num * 200;// 正向移动moveServo(DEFAULT_START_ANGLE, DEFAULT_END_ANGLE, DEFAULT_STEPS, startTime, totalTime);startTime = millis();// 反向移动moveServo(DEFAULT_END_ANGLE, DEFAULT_START_ANGLE, DEFAULT_STEPS, startTime, totalTime);}
}// 移动舵机的辅助函数
void moveServo(int startAngle, int endAngle, int steps, unsigned long startTime, int totalTime)
{unsigned long stepDuration = totalTime / steps;float deltaAngle = (endAngle - startAngle) * 0.5;for (int i = 0; i <= steps; i++){float t = (float)i / steps;int angle = startAngle + deltaAngle * (1 - cos(t * PI));myservo.write(angle);delay(totalTime / steps);// 非阻塞延时while (millis() - startTime < (i + 1) * stepDuration){checkButton();yield(); // 或者其他适合的多任务处理方法}}
}// 控制LED闪烁
void led_blink_once()
{unsigned long currentMillis = millis();// 控制LED1闪烁一次blinkLEDOnce(LED_0, led0State, previousMillis1, currentMillis, led_interval, led0Blinked);// 控制LED2闪烁一次blinkLEDOnce(LED_1, led1State, previousMillis2, currentMillis, led_interval, led1Blinked);
}// 闪烁LED一次
void blinkLEDOnce(int pin, bool &state, unsigned long &previousMillis, unsigned long currentMillis, long led_interval, bool &blinked)
{if (!blinked){if (currentMillis - previousMillis >= led_interval){previousMillis = currentMillis;state = !state; // 切换LED状态digitalWrite(pin, state);if (state == LOW){blinked = true; // 标记LED已经闪烁}}}
}

 

 

相关文章:

【小制作】米家模拟手指点击

代码功能解释 这段代码是一个基于Arduino平台的控制程序&#xff0c;主要功能包括&#xff1a; 初始化&#xff1a;设置引脚模式、初始化编码器、舵机和EEPROM。按键检测&#xff1a;处理按钮的单击、双击和长按事件&#xff0c;并根据事件执行相应操作。编码器更新&#xff…...

【深度学习入门_基础篇】线性代数本质

开坑本部分主要为基础知识复习&#xff0c;新开坑中&#xff0c;学习记录自用。 学习目标&#xff1a; 熟悉向量、线性组合、线性变换、基变换、矩阵运算、逆函数、秩、列空间、零空间、范式、特征指、特征向量等含义与应用。 强烈推荐此视频&#xff1a; 【官方双语/合集】…...

047_小驰私房菜_Qcom 8系列,Jpeg GPU 旋转

【问题背景】 横屏模式下&#xff0c;发现有些三方app拍照旋转了90度。 【修改策略】 adb shell setprop endor.debug.camera.overrideGPURotationUsecase 1 或者在/vendor/etc/camera/camxoverridesettings.txt 里面添加如下内容 overrideGPURotationUsecase1 【解释】 Ga…...

Elasticsearch 操作文档对数据的增删改查操作 索引库文档 操作数据 CRUD

介绍 在 Elasticsearch 中&#xff0c;文档的增、删、改、查操作是核心的基本功能。Elasticsearch 使用 RESTful API 提供这些操作&#xff0c;通常通过 HTTP 请求与 Elasticsearch 集群进行交互。 索引库 {"mappings": {"properties": {"title&qu…...

最新MySQL面试题(2025超详细版)

2025最新超详细MySQL面试题 文章目录 2025最新超详细MySQL面试题[toc]一、 SQL 和基本操作1. SQL的执行顺序2. 如何优化MySQL查询3. 常用的聚合函数4. 数据库事务5. 事务的四大特性(ACID)6. 视图7. MySQL中使用LIMIT子句进行分页8. MySQL中使用变量和用户定义的函数9. MySQL中的…...

使用MPTCP+BBR进行数据传输,让网络又快又稳

1.前言 在前文《链路聚合技术——多路径传输Multipath TCP(MPTCP)快速实践》中我们使用mptcpize run命令实现了两个节点间通信使用MPTCP协议进行传输&#xff0c;并实现了传输速率的聚合。 实际应用中更推荐原生支持mptcp的应用&#xff0c;在MPTCP官网中可以看到如TCPDump、…...

滴滴数据分析80道面试题及参考答案

如何衡量分类好坏? 衡量分类好坏有多种方法,常用的有准确率、精确率、召回率、F1 值、ROC 曲线与 AUC 值等。 准确率:是指分类正确的样本数占总样本数的比例,计算公式为:准确率 = (分类正确的样本数)/(总样本数)。准确率越高,说明分类器整体的分类效果越好,但在正负…...

基于物联网疫苗冷链物流监测系统设计

1. 项目开发背景 随着全球对疫苗运输要求的提高&#xff0c;特别是针对温度敏感型药品&#xff08;如疫苗&#xff09;的冷链管理&#xff0c;如何保证疫苗在运输过程中的温度、湿度、震动等环境因素的稳定性已成为亟需解决的问题。疫苗运输过程中&#xff0c;任何温度或湿度的…...

计算机网络基础(7)中科大郑铨老师笔记

应用层 目标&#xff1a;  网络应用的 原理&#xff1a;网络应用协议的概念和实现方面 传输层的服务模型 客户-服务器模式 对等模式(peerto-peer) 内容分发网络  网络应用的 实例&#xff1a;互联网流行的应用层协 议  HTTP  FTP  SMTP / POP3 / IMAP  DNS…...

GOGOGO 抽象

抽象其实也算面向对象特征之一 抽象 含义&#xff1a;当多个子类中的共性向上提取&#xff0c;父类中不知道如何写具体实现&#xff0c;因为提取的共性并不一定能解决子类中实现的功能【同结构不一定同实现代码体】&#xff0c;就需要抽象概念 作用 父类只抽取结构&#xff…...

STM32-笔记26-WWDG窗口看门狗

一、简介 窗口看门狗用于监测单片机程序运行时效是否精准&#xff0c;主要检测软件异常&#xff0c;一般用于需要精准检测程序运行时间的场合。 窗口看门狗的本质是一个能产生系统复位信号和提前唤醒中断的6位计数器&#xff08;有的地方说7位。其实都无所谓&#xff0…...

10.装饰器

装饰器的基本用法创建简单的装饰器带参数的装饰器装饰器链类装饰器内置装饰器 定义:装饰器是一个函数&#xff0c;它接受另一个函数作为参数&#xff0c;并返回一个新的函数。装饰器通常用于在函数执行前后添加额外的功能&#xff0c;如日志记录、权限检查、性能测试等。 1.基…...

uniapp H5页面实现懒加载

在 uniapp 中&#xff0c;要在小的 view 内实现列表懒加载&#xff0c;可以通过以下步骤来实现&#xff1a; 使用 scroll-view 组件来创建一个可滚动的区域。在 scroll-view内 部放置一个list组件&#xff0c;用于显示数据列表。监听 scroll-view 的滚动事件&#xff0c;当滚动…...

STM32使用UART发送字符串与printf输出重定向

首先我们先看STM32F103C8T6的电路图 由图可知&#xff0c;其PA9和PA10引脚分别为UART的TX和RX(注意&#xff1a;这个电路图是错误的&#xff0c;应该是PA9是X而PA9是RX&#xff0c;我们看下图的官方文件可以看出)&#xff0c;那么接下来我们应该找到该引脚的定义是什么&#xf…...

NLP初识

目录 0简介一、自然语言概述1. 什么是NLP?2. NLP常用工具0简介 NLP系列开始更新了!!!这个系列主要会介绍一些NLP的基础概念,比如RNN、LSTM、GRU等内容,重头戏放在大语言模型的基础讲解上,其中大语言模型的分享主要由两个方面:1.基础结构(Seq2Seq,Attention,Transfor…...

解決當前IP地址僅適用於本地網路

想要解決“當前IP地址僅適用於本地網路”其實並不困難。本篇文章將介紹其發生的原因以及如何解決。 “僅限本地網路”是什麼意思&#xff1f; 當IP地址為“僅限本地網路”時&#xff0c;意味著設備正在使用私人網路內部IP地址&#xff0c;但無法連接到互聯網。如果將本地IP視…...

Eplan 项目结构(高层代号、安装地点、位置代号)

Eplan中的项目结构分为3个层次&#xff1a; &#xff08;1&#xff09;功能面结构。指明这个系统的功能&#xff0c;有什么用途。在EPlan中&#xff0c;指的就是"高层代号&#xff08;&#xff09;"。 一般指的是线体。 &#xff08;2&#xff09;位置面结构。指明该…...

文献分享集:跨模态的最邻近查询RoarGraph

文章目录 1. \textbf{1. } 1. 导论 1.1. \textbf{1.1. } 1.1. 研究背景 1.2. \textbf{1.2. } 1.2. 本文的研究 1.3. \textbf{1.3. } 1.3. 有关工作 2. \textbf{2. } 2. 对 OOD \textbf{OOD} OOD负载的分析与验证 2.1. \textbf{2.1. } 2.1. 初步的背景及其验证 2.1.1. \textbf{2…...

xdoj 判断字符串子串

判断字符串子串 问题描述 编写程序&#xff1a; 判断一个不大于 20 个字符的字符串是否是另一个不大于 20 个字符的字符串的子 串&#xff0c;如果是&#xff0c;则输出子串在父串的起始位置, 如果不是子串&#xff0c;则输出 No!。 输入说明 输入分 2 行&#xff1a; 第…...

n8n - AI自动化工作流

文章目录 一、关于 n8n关键能力n8n 是什么意思 二、快速上手 一、关于 n8n n8n是一个具有原生AI功能的工作流自动化平台&#xff0c;它为技术团队提供了代码的灵活性和无代码的速度。凭借400多种集成、原生人工智能功能和公平代码许可证&#xff0c;n8n可让您构建强大的自动化…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...