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

代码功能解释

这段代码是一个基于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() {
    // 写入EEPROM
    EEPROM.write(0, fast_num);
    EEPROM.write(1, fast_delta);
        // 写完EEPROM后两颗LED交替闪烁两次
    blinkLEDsAlternatingTwice();
}
void read_eeprom() {
    // 读取EEPROM
    fast_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已经闪烁
      }
    }
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux-Ubuntu之I2C通信

Linux-Ubuntu之I2C通信 一&#xff0c;I2C通信原理1.写时序2.读时序 二&#xff0c;代码实现三&#xff0c;显示 一&#xff0c;I2C通信原理 使用I2C接口驱动AP3216C传感器&#xff0c;该传感器能实现两个效果&#xff0c;一个是感应光强&#xff0c;另一个是探测物体与传感器…

音视频入门基础:MPEG2-PS专题(4)——FFmpeg源码中,判断某文件是否为PS文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.ps 可以判断出某个文件是否为PS文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为PS文件呢&#xff1f;它内部其实是通过mpegps_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_format3函数和AVI…

框架模块说明 #09 日志模块_01

背景 日志模块是系统的重要组成部分&#xff0c;主要负责记录系统运行状态和定位错误问题的功能。通常&#xff0c;日志分为系统日志、操作日志和安全日志三类。虽然分布式数据平台是当前微服务架构中的重要部分&#xff0c;但本文的重点并不在此&#xff0c;而是聚焦于自定义…

【数据仓库】hadoop3.3.6 安装配置

文章目录 概述下载解压安装伪分布式模式配置hdfs配置hadoop-env.shssh免密登录模式设置初始化HDFS启动hdfs配置yarn启动yarn 概述 该文档是基于hadoop3.2.2版本升级到hadoop3.3.6版本&#xff0c;所以有些配置&#xff0c;是可以不用做的&#xff0c;下面仅记录新增操作&#…

算法题(25):只出现一次的数字(三)

审题&#xff1a; 该题中有两个元素只出现一次并且其他元素都出现两次&#xff0c;需要返回这两个只出现一次的数&#xff0c;并且不要求返回顺序 思路: 由于对空间复杂度有要求&#xff0c;我们这里不考虑哈希表。我们采用位运算的方法解题 方法&#xff1a;位运算 首先&#…

将机器学习预测模型融入AI agent的尝试(一)

将机器学习临床预测模型融入AI agent的尝试&#xff08;一&#xff09; 我主要是使用机器学习制作临床预测模型和相关的应用&#xff0c;最近考虑的事情是自己之前的的工作能不能和AI agent进行融合&#xff0c;将AI 对自然语言理解能力和预测模型的预测能力结合在一起&#x…

51单片机——按键实验

由于机械点的弹性作用&#xff0c;按键开关在闭合时不会马上稳定的接通&#xff0c;在断开时也不会一下子断开&#xff0c;因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的&#xff0c;一般为 5ms 到 10ms&#xff0c;为了确保 CPU 对按键的…

电子邮件对网络安全的需求

&#xff08; 1&#xff09;机密性&#xff1a;传输过程中不被第三方阅读到邮件内容&#xff0c;只有真正的接收方才可以阅读邮件。&#xff08; 1.5 分&#xff09; &#xff08; 2&#xff09;完整性&#xff1a;支持在邮件传输过程中不被篡改&#xff0c;若发生篡改&#…

vue路由模式面试题

vue路由模式 1.路由的模式有哪些?有什么区别? history和hash模式 区别: 1.表现的形态不同: 在地址栏url中:hash模式中带有**#**号,history没有 2.请求错误时表现不同: 在hash模式中,对于404地址请求时,不会进行请求 但是在history模式中,对于404请求时,仍然会进行请求…

电子应用设计方案86:智能 AI背景墙系统设计

智能 AI 背景墙系统设计 一、引言 智能 AI 背景墙系统旨在为用户创造一个动态、个性化且具有交互性的空间装饰体验&#xff0c;通过融合先进的技术和创意设计&#xff0c;提升室内环境的美观度和功能性。 二、系统概述 1. 系统目标 - 提供多种主题和风格的背景墙显示效果&…

Python爬虫 - 豆瓣图书数据爬取、处理与存储

文章目录 前言一、使用版本二、需求分析1. 分析要爬取的内容1.1 分析要爬取的单个图书信息1.2 爬取步骤1.2.1 爬取豆瓣图书标签分类页面1.2.2 爬取分类页面1.2.3 爬取单个图书页面 1.3 内容所在的标签定位 2. 数据用途2.1 基础分析2.2 高级分析 3. 应对反爬机制的策略3.1 使用 …

西安电子科技大学初/复试笔试、面试、机试成绩占比

西安电子科技大学初/复试笔试、面试、机试成绩占比 01通信工程学院 02电子工程学院 03计算机科学与技术学院 04机电工程学院 06经济与管理学院 07数学与统计学院 08人文学院 09外国语学院 12生命科学与技术学院 13空间科学与技术学院 14先进材料与纳米科技学院 15网络与信息安…

多模态论文笔记——CogVLM和CogVLM2(副)

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍多模态模型的LoRA版本——CogVLM和CogVLM2。在SD 3中使用其作为captioner基准模型的原因和优势。 文章目录 CogVLM论文背景VLMs 的任务与挑战现有方法及…

智慧工地信息管理与智能预警平台

建设背景与政策导向 智慧工地信息管理与智能预警平台的出现&#xff0c;源于工地管理面临的诸多挑战&#xff0c;如施工地点分散、危险区域多、监控手段落后等。随着政府对建筑产业现代化的积极推动&#xff0c;各地纷纷出台政策支持智慧工地的发展&#xff0c;旨在通过信息技…

【从零开始入门unity游戏开发之——C#篇42】C#补充知识——随机数(Random)、多种方法实现string字符串拼接、语句的简写

文章目录 一、随机数1、Random.Next()生成随机整数示例&#xff1a;生成一个随机整数生成指定范围内的随机整数 2、Random.NextSingle生成随机浮点数示例&#xff1a;生成随机浮点数 3、 生成随机字母或字符示例&#xff1a;生成随机字母示例&#xff1a;生成随机小写字母 二、…

「Mac畅玩鸿蒙与硬件54」UI互动应用篇31 - 滑动解锁屏幕功能

本篇教程将实现滑动解锁屏幕功能&#xff0c;通过 Slider 组件实现滑动操作&#xff0c;学习事件监听、状态更新和交互逻辑的实现方法。 关键词 滑动解锁UI交互状态管理动态更新事件监听 一、功能说明 滑动解锁屏幕功能包含以下功能&#xff1a; 滑动解锁区域&#xff1a;用…

VScode SSH 错误:Got bad result from install script 解決

之前vscode好好的&#xff0c;某天突然连接报错如下 尝试1. 服务器没有断开,ssh可以正常连接 2. 用管理员权限运行vscode&#xff0c;无效 3. 删除服务器上的~/.vscode-server 文件夹&#xff0c;无效 试过很多后&#xff0c;原来很可能是前一天anaconda卸载导致注册表项 步…

[论文笔记]Representation Learning with Contrastive Predictive Coding

引言 今天带来论文 Representation Learning with Contrastive Predictive Coding的笔记。 提出了一种通用的无监督学习方法从高维数据中提取有用表示&#xff0c;称为对比预测编码(Contrastive Predictive Coding,CPC)。使用了一种概率对比损失&#xff0c; 通过使用负采样使…

【C#深度学习之路】如何使用C#实现Yolo5/8/11全尺寸模型的训练和推理

【C#深度学习之路】如何使用C#实现Yolo5/8/11全尺寸模型的训练和推理 项目背景项目实现调用方法项目展望写在最后项目下载链接 本文为原创文章&#xff0c;若需要转载&#xff0c;请注明出处。 原文地址&#xff1a;https://blog.csdn.net/qq_30270773/article/details/1449186…

如何很快将文件转换成另外一种编码格式?编码?按指定编码格式编译?如何检测文件编码格式?Java .class文件编码和JVM运行期内存编码?

如何很快将文件转换成另外一种编码格式? 利用VS Code右下角的"选择编码"功能&#xff0c;选择"通过编码保存"可以很方便将文件转换成另外一种编码格式。尤其&#xff0c;在测试w/ BOM或w/o BOM, 或者ANSI编码和UTF编码转换&#xff0c;特别方便。VS文件另…