1. 项目概述:一个能“数数”的俯卧撑计数器
如果你和我一样,是个健身爱好者,或者想督促自己养成锻炼习惯,那你肯定遇到过这样的问题:做俯卧撑时,数着数着就忘了自己做了多少个。特别是做到力竭时,脑子里想的全是“再坚持一个”,结果回过神来,刚才到底做了20个还是25个?完全没印象了。手动计数不仅容易分心,还影响锻炼节奏和效果。
今天分享的这个“基于Arduino和超声波传感器的俯卧撑计数器”项目,就是为了解决这个痛点而生的。它本质上是一个智能化的动作计数装置,核心原理是利用超声波传感器测量你身体起伏时距离地面的高度变化,通过Arduino板卡进行逻辑判断,从而自动、准确地记录你的俯卧撑次数。这个项目非常适合Arduino初学者和电子爱好者上手,因为它硬件简单(核心就两样),逻辑清晰,但又能实实在在地解决一个生活小问题,成就感满满。
从技术角度看,它巧妙地将物理距离测量(超声波测距)转化为数字逻辑(计数判断),是学习传感器应用、条件判断和状态机编程的绝佳范例。无论你是想给自己做个健身小助手,还是想找个有趣的Arduino练手项目,这个俯卧撑计数器都值得一试。接下来,我会从设计思路、硬件搭建、代码编写到调试优化,完整拆解这个项目的每一个环节,并附上我实际制作过程中踩过的坑和总结的技巧。
2. 核心硬件选型与电路设计思路
一个项目的成功,一半取决于前期的方案设计。对于这个俯卧撑计数器,我们需要明确它的工作场景和需求,从而选择合适的硬件并设计出可靠的电路。
2.1 硬件清单与选型理由
这个项目的硬件构成极其精简,但每一件都至关重要。下面是我推荐的清单及选型背后的考量:
1. 主控板:Arduino Uno R3
- 为什么是Uno?对于绝大多数入门和中等复杂度的项目,Arduino Uno是性价比和易用性的平衡点。它拥有14个数字I/O口和6个模拟输入口,对于本项目(1个传感器、1个显示屏、1个蜂鸣器)绰绰有余。其ATmega328P芯片性能足够,社区支持最完善,遇到任何问题都能轻松找到解决方案。虽然像Nano更小巧,但Uno的稳定性和便于插拔的特性,在原型开发阶段更具优势。
- 避坑提示:市面上有大量兼容板,建议选择口碑好的品牌,避免因电源管理或USB芯片不稳定导致的莫名故障。
2. 测距传感器:HC-SR04超声波模块
- 核心原理:该模块通过发射40kHz的超声波并接收其回波,根据时间差计算距离。公式很简单:距离 = (声速 × 时间差) / 2。在空气中声速受温度影响,但对于俯卧撑计数这种精度要求(厘米级)的应用,常温下的近似值(340m/s)完全够用。
- 选型理由:HC-SR04成本极低(通常不到10元)、量程合适(2cm-400cm)、接口简单(仅需4个引脚:VCC, GND, Trig, Echo),是入门级距离测量的首选。相比红外、激光等方案,超声波不受环境光线影响,且测量面是一个锥形区域,容错性更好。
- 关键参数:触发信号(Trig)需要至少10微秒的高电平,回响信号(Echo)输出高电平的持续时间即对应距离。测量周期建议在60ms以上,避免声波串扰。
3. 输出设备:0.96寸OLED显示屏 (I2C接口) & 有源蜂鸣器
- OLED显示屏:选择I2C接口的0.96寸OLED,主要是为了节省宝贵的I/O口。I2C只需两根线(SDA, SCL)就能通信,且库支持成熟(如
Adafruit_SSD1306和Adafruit_GFX)。显示屏用于实时显示距离、计数、组数等信息,视觉反馈直观。 - 有源蜂鸣器:用于提供听觉反馈。当计数有效时,“滴”一声确认;当完成一组时,可以发出不同频率的提示音。有源蜂鸣器只需给高电平就会响,驱动简单。注意要串联一个220Ω的限流电阻保护IO口。
4. 其他:面包板、杜邦线(公对公、公对母)、USB数据线、一个用于固定传感器的小支架(可以用乐高积木或3D打印件)。
2.2 电路连接图与安全要点
电路连接是项目的骨架,正确的连接是后续一切工作的基础。下图清晰地展示了各元件如何与Arduino Uno协同工作:
flowchart TD subgraph A [电源与核心控制] direction LR A1[5V电源] --> Arduino A2[GND地线] --> Arduino end subgraph B [超声波测距模块 HC-SR04] B1[VCC] -->|连接至| A1 B2[GND] -->|连接至| A2 B3[Trig] -->|连接至数字引脚 9| Arduino B4[Echo] -->|连接至数字引脚 10| Arduino end subgraph C [显示模块 OLED I2C] C1[VCC] -->|连接至| A1 C2[GND] -->|连接至| A2 C3[SDA] -->|连接至模拟引脚 A4| Arduino C4[SCL] -->|连接至模拟引脚 A5| Arduino end subgraph D [声音反馈模块 有源蜂鸣器] D1[正极+] -->|串联220Ω电阻后<br>连接至数字引脚 8| Arduino D2[负极-] -->|连接至| A2 end Arduino -->|处理信号并驱动| B Arduino -->|发送显示数据| C Arduino -->|输出提示音信号| D连接步骤与安全注意事项:
- 先断电操作:在连接任何线路前,确保Arduino未通过USB线连接电脑或电源。
- 电源优先:先将所有元件的
VCC(或+、Vin)连接到Arduino的5V引脚,所有GND连接到Arduino的任一GND引脚。确保共地,这是电路正常工作的前提。 - 信号线连接:参照上图,将传感器的Trig、Echo,显示屏的SDA、SCL,蜂鸣器的信号线连接到指定的数字引脚。务必核对引脚编号,接错可能导致设备不工作甚至损坏。
- 蜂鸣器加电阻:驱动有源蜂鸣器时,必须在信号线(正极)上串联一个220Ω的电阻,直接连接IO口可能因电流过大而损坏Arduino芯片。
- 检查短路:连接完成后,目视检查面包板上的杜邦线金属头有无相互触碰,特别是电源正极(5V)和地线(GND)之间绝对不能短路。
3. 核心算法与状态机设计
硬件是身体,软件是灵魂。让这个计数器“聪明”地识别一次俯卧撑,而不是把任何距离波动都算上,是代码部分的核心挑战。这里的关键在于设计一个合理的“状态机”。
3.1 俯卧撑动作的数学模型与阈值设定
首先,我们需要量化一个标准的俯卧撑动作。假设人趴下时,胸口到地面的距离为D_low(例如10cm);撑起时,胸口到地面的距离为D_high(例如30cm)。那么一次完整的俯卧撑,就是距离从D_high下降到D_low(下放),再回升到D_high(撑起)的过程。
但是,我们无法要求用户每次都能精确地做到同一个高度。因此,需要引入“阈值”的概念,形成一个滞回区间,以提高识别的鲁棒性(容错性)。
- 上限阈值 (Upper_Threshold):例如设置为25cm。当距离大于此值时,我们认为身体处于“高位”或“起始”状态。
- 下限阈值 (Lower_Threshold):例如设置为15cm。当距离小于此值时,我们认为身体处于“低位”或“触底”状态。
- 滞回区间:在25cm和15cm之间,是一个“缓冲带”。只有当距离从高于25cm穿过缓冲带,并低于15cm时,才标记“已下放”;只有当距离从低于15cm穿过缓冲带,并高于25cm时,才标记“已撑起”。这样可以有效过滤掉在高位或低位附近的小幅抖动。
如何确定你的个人阈值?这没有标准答案,因为每个人的臂长、身体厚度不同。在代码中,我会编写一个“校准模式”,让设备先学习你的动作幅度。你只需分别做出撑起和趴下的姿势,程序会自动记录这两个距离值,并以此为基础计算阈值(例如,高位距离-5cm作为上限阈值,低位距离+5cm作为下限阈值)。这是让设备适应你,而不是你去适应设备的关键。
3.2 状态机:让程序理解“一个回合”
状态机是解决这类逻辑问题的利器。我们可以把一次俯卧撑识别定义为四个状态:
状态0:等待开始 (WAIT_FOR_TOP)
- 条件:系统初始状态,或完成上一次计数后的状态。
- 行为:持续监测距离。只有当距离首次大于
Upper_Threshold时,才认为用户准备好了,进入状态1。
状态1:已到达高位,等待下放 (TOP_REACHED)
- 条件:已检测到高位。
- 行为:监测距离是否下降。只有当距离小于
Lower_Threshold时,才表明一次有效的“下放”动作完成,进入状态2。在此期间,即使距离又高于上限阈值,也不回退到状态0,防止误判。
状态2:已到达低位,等待撑起 (BOTTOM_REACHED)
- 条件:已检测到低位(下放完成)。
- 行为:监测距离是否回升。只有当距离再次大于
Upper_Threshold时,才表明一次有效的“撑起”动作完成,一个完整的俯卧撑周期结束。 - 动作:计数器
pushUpCount加1。蜂鸣器发出短促“滴”声提示。显示屏更新计数。然后,状态机回到状态1,等待下一次“下放”。注意,不是回到状态0,因为在高位时可能连续做下一个。
为什么状态机优于简单的“过阈值”计数?假设你只用一个阈值(比如20cm),规定低于它就计数。那么当你身体在20cm附近颤抖时,可能会连续触发多次计数。而状态机强制要求了动作的顺序:必须“高 -> 低 -> 高”才算一次,并且中间有滞回区间作为缓冲,从根本上避免了抖动和误触发。
4. 软件实现与代码逐行解析
有了清晰的设计,现在让我们把思路转化为Arduino代码。我将使用PlatformIO(或Arduino IDE)进行开发,并依赖NewPing和Adafruit_SSD1306这两个优秀的库来简化超声波和OLED的操作。
4.1 开发环境搭建与库管理
首先,确保你已安装Arduino IDE或VS Code with PlatformIO。我强烈推荐PlatformIO,因为它有更好的库管理和项目结构。
库安装:
- NewPing库:在库管理器中搜索“NewPing”并安装。这个库提供了非阻塞式的超声波测距函数,比直接操作
pulseIn更高效、准确,避免了程序在等待回波时“卡住”。 - Adafruit SSD1306 & GFX库:搜索并安装“Adafruit SSD1306”和“Adafruit GFX”。这是驱动I2C OLED屏的标准库。
项目结构:创建一个新的项目,主要的代码将写在src/main.cpp(PlatformIO)或.ino文件(Arduino IDE)中。
4.2 核心代码实现与注释
以下是完整的、带有详细注释的代码。你可以直接复制使用,但更重要的是理解每一部分的作用。
/* * 俯卧撑计数器 - 基于状态机的鲁棒性实现 * 硬件:Arduino Uno, HC-SR04, I2C OLED, 有源蜂鸣器 * 库:NewPing, Adafruit_SSD1306, Adafruit_GFX */ #include <NewPing.h> // 超声波库 #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // OLED库 // ==================== 硬件引脚定义 ==================== #define TRIG_PIN 9 #define ECHO_PIN 10 #define MAX_DISTANCE 100 // 超声波最大测量距离(厘米),俯卧撑场景足够 #define BUZZER_PIN 8 // 蜂鸣器引脚 // ==================== 对象初始化 ==================== NewPing sonar(TRIG_PIN, ECHO_PIN, MAX_DISTANCE); // 超声波传感器对象 Adafruit_SSD1306 display(128, 64, &Wire, -1); // OLED对象,128x64分辨率 // ==================== 全局变量与状态定义 ==================== int pushUpCount = 0; // 俯卧撑计数 int setCount = 0; // 组数计数(可选功能) int distance = 0; // 当前测量距离 // 状态机状态定义 enum State { WAIT_FOR_TOP, // 0: 等待到达高位(起始/准备) TOP_REACHED, // 1: 已在高位,等待下放 BOTTOM_REACHED // 2: 已在低位,等待撑起 }; State currentState = WAIT_FOR_TOP; // 初始状态 // 阈值参数(单位:厘米) // !!!重要:这些值需要通过后面的“校准模式”确定!!! int UPPER_THRESHOLD = 25; // 上限阈值,高于此值为“高位” int LOWER_THRESHOLD = 15; // 下限阈值,低于此值为“低位” unsigned long lastDebounceTime = 0; // 用于防抖的计时器 const unsigned long DEBOUNCE_DELAY = 50; // 防抖延时50毫秒 // ==================== 函数声明 ==================== void beep(int duration); // 蜂鸣器提示音函数 void updateDisplay(); // 更新OLED显示 void calibrate(); // 进入校准模式 // ==================== Setup函数:初始化 ==================== void setup() { Serial.begin(115200); // 开启串口调试,便于观察数据 pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); // 初始化OLED显示屏 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C地址通常是0x3C或0x3D Serial.println(F("SSD1306分配失败")); for(;;); // 如果初始化失败,程序停在这里 } display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println("Push-Up Counter"); display.println("Initializing..."); display.display(); delay(1000); // 开机提示音 beep(100); delay(50); beep(100); // 询问是否进入校准模式(通过串口) Serial.println("系统启动完成。"); Serial.println("是否需要校准阈值?(输入 'y' 进入校准,其他键跳过)"); delay(3000); // 等待3秒让用户决定 if(Serial.available() > 0){ char cmd = Serial.read(); if(cmd == 'y' || cmd == 'Y'){ calibrate(); // 进入校准函数 } } updateDisplay(); // 显示初始界面 } // ==================== Loop函数:主循环 ==================== void loop() { // 1. 非阻塞式获取距离(NewPing库的优势) delay(30); // 每次循环间隔约30ms,保证测量稳定 distance = sonar.ping_cm(); // 获取距离,单位厘米 // 过滤无效读数(0表示超出量程或测量失败) if(distance == 0 || distance > MAX_DISTANCE) { return; // 跳过本次循环,等待下一次有效数据 } // 2. 状态机逻辑处理(核心) switch(currentState) { case WAIT_FOR_TOP: // 只有当距离持续大于上限阈值一段时间,才认为稳定在高位 if(distance > UPPER_THRESHOLD) { if(millis() - lastDebounceTime > DEBOUNCE_DELAY) { currentState = TOP_REACHED; lastDebounceTime = millis(); Serial.println("状态: 已检测到高位 (TOP_REACHED)"); } } else { lastDebounceTime = millis(); // 不满足条件,重置防抖计时 } break; case TOP_REACHED: // 已在高位,等待下降到低位 if(distance < LOWER_THRESHOLD) { if(millis() - lastDebounceTime > DEBOUNCE_DELAY) { currentState = BOTTOM_REACHED; lastDebounceTime = millis(); Serial.println("状态: 已下放到低位 (BOTTOM_REACHED)"); } } else { lastDebounceTime = millis(); } break; case BOTTOM_REACHED: // 已在低位,等待回升到高位 if(distance > UPPER_THRESHOLD) { if(millis() - lastDebounceTime > DEBOUNCE_DELAY) { // 一个完整的“高-低-高”周期完成! pushUpCount++; // 计数增加 beep(150); // 发出确认音效 Serial.print("计数+1! 当前总数: "); Serial.println(pushUpCount); currentState = TOP_REACHED; // 回到“等待下放”状态,准备下一个 lastDebounceTime = millis(); updateDisplay(); // 更新屏幕显示 } } else { lastDebounceTime = millis(); } break; } // 3. (可选)每10次蜂鸣提示一组完成 if(pushUpCount > 0 && pushUpCount % 10 == 0) { static int lastSet = 0; // 静态变量,记录上次提示的组数 if(pushUpCount / 10 > lastSet) { lastSet = pushUpCount / 10; for(int i=0; i<3; i++){ // 响三声提示 beep(80); delay(120); } setCount = lastSet; updateDisplay(); } } // 4. 将实时距离输出到串口监视器,用于调试 static unsigned long lastPrintTime = 0; if(millis() - lastPrintTime > 200) { // 每200ms打印一次,避免刷屏 lastPrintTime = millis(); Serial.print("距离: "); Serial.print(distance); Serial.print("cm | 状态: "); Serial.println(currentState); } } // ==================== 功能函数实现 ==================== // 蜂鸣器发声函数 void beep(int duration) { digitalWrite(BUZZER_PIN, HIGH); delay(duration); digitalWrite(BUZZER_PIN, LOW); } // 更新OLED显示 void updateDisplay() { display.clearDisplay(); display.setCursor(0, 0); display.setTextSize(2); // 大字体显示计数 display.print("Count:"); display.println(pushUpCount); display.setTextSize(1); display.setCursor(0, 30); display.print("Dist:"); display.print(distance); display.println(" cm"); display.setCursor(0, 45); display.print("Set:"); display.println(setCount); display.setCursor(0, 55); display.print("State:"); display.println(currentState); // 显示当前状态(调试用) display.display(); } // 校准模式函数 void calibrate() { display.clearDisplay(); display.setCursor(0,0); display.println("===CALIBRATION==="); display.println("1. Assume TOP"); display.println(" position."); display.display(); beep(300); delay(2000); // 给用户2秒时间准备 int topDistance = 0; for(int i=0; i<10; i++){ // 采样10次取平均,减少误差 topDistance += sonar.ping_cm(); delay(100); } topDistance /= 10; display.clearDisplay(); display.println("TOP recorded:"); display.print(topDistance); display.println(" cm"); display.display(); delay(1500); display.clearDisplay(); display.println("2. Assume BOTTOM"); display.println(" position."); display.display(); beep(300); delay(2000); int bottomDistance = 0; for(int i=0; i<10; i++){ bottomDistance += sonar.ping_cm(); delay(100); } bottomDistance /= 10; display.clearDisplay(); display.println("BOT recorded:"); display.print(bottomDistance); display.println(" cm"); display.display(); delay(1500); // 基于测量值自动计算阈值(留有缓冲余量) UPPER_THRESHOLD = topDistance - 3; // 比最高位低3cm作为上限阈值 LOWER_THRESHOLD = bottomDistance + 3; // 比最低位高3cm作为下限阈值 display.clearDisplay(); display.println("Calibration OK!"); display.print("Upper:"); display.print(UPPER_THRESHOLD); display.println(" cm"); display.print("Lower:"); display.print(LOWER_THRESHOLD); display.println(" cm"); display.display(); delay(2500); // 将校准后的阈值保存到EEPROM(可选,实现掉电保存) // EEPROM.write(0, UPPER_THRESHOLD); // EEPROM.write(1, LOWER_THRESHOLD); }4.3 代码关键点解析与调试技巧
- 非阻塞测距:
NewPing库的ping_cm()函数内部已经处理了时序,不会像pulseIn()那样阻塞程序运行。这是保证状态机能及时响应的基础。 - 防抖机制:在状态切换的判断中,我加入了
millis() - lastDebounceTime > DEBOUNCE_DELAY的条件。这意味着距离必须持续超过阈值一段时间(50ms),状态才会改变。这能有效滤除因传感器噪声或身体微小晃动造成的瞬时误触发。 - 串口调试:代码中大量使用
Serial.print()输出状态和距离信息。在开发时,务必打开Arduino IDE的串口监视器(波特率设为115200),这是你观察程序“内心想法”、排查逻辑错误的最重要窗口。你可以看到实时的距离数据和状态变迁。 - 校准模式:
calibrate()函数引导用户分别做出高位和低位姿势,自动计算阈值。这是项目能否适应不同用户的关键。更进阶的做法是将计算出的阈值保存到Arduino的EEPROM中,这样即使断电重启也无需重新校准。 - 状态显示:在OLED上显示
State对于调试非常有帮助。在实际稳定使用后,可以将其从显示界面中移除,只保留计数和距离信息。
5. 机械结构与实际部署要点
代码烧录成功后,别急着开始锻炼。如何布置硬件,直接影响计数的准确性。
5.1 传感器安装与角度调整
这是整个项目最容易出问题的一环。HC-SR04的超声波波束角大约为15度,形成一个圆锥形的探测区域。
- 错误示范:将传感器平放在地面,垂直向上探测。这样你的身体可能会部分在探测锥外,或者探测到的是你的头部、肩膀等不同部位,距离数据跳动会非常大。
- 正确做法:制作一个倾斜的支架,让传感器以一定角度(如30-45度)斜向上指向你胸口预计的运动轨迹中心点。这样在整个俯卧撑过程中,胸口始终在波束中心附近,测得的距离变化曲线更平滑、一致。
- 安装位置:传感器应放置在你身体正下方稍靠前的位置(约胸口正对的地面)。可以用双面胶或蓝丁胶将其临时固定在支架上,方便微调。
- 实测技巧:打开串口绘图器(Arduino IDE -> 工具 -> 串口绘图器),做几个俯卧撑,观察距离随时间变化的曲线。一个理想的曲线应该是一个清晰的、平滑的波浪形。如果曲线毛刺多或形状不规则,就需要调整传感器的角度和位置。
5.2 环境干扰排除与优化
超声波传感器虽然抗光干扰能力强,但对某些物理环境很敏感。
- 软表面干扰:如果你在地毯或瑜伽垫上锻炼,超声波打到这些吸音材料上,回波信号会很弱,导致测量距离不准甚至失败。解决方案:在传感器正前方的地面上,放置一块硬质的、平整的小板(如一本硬壳书、一块小木板),确保声波能被有效反射回来。
- 多径反射:如果传感器靠近墙壁或家具,声波可能通过墙壁反射后再回来,导致测出错误的大距离。解决方案:尽量在开阔空间使用,或确保传感器与侧面障碍物保持一定距离。
- 供电不足:所有传感器和显示屏都从Arduino的5V引脚取电,在做大电流动作时(如驱动多个元件),可能导致电压瞬间下降,引起复位或读数异常。解决方案:为Arduino使用独立的、电流充足的5V电源适配器供电,而不是仅靠电脑USB口。或者,考虑为OLED屏单独供电。
6. 常见问题排查与功能扩展
即使按照步骤操作,你也可能会遇到一些问题。这里是我在多次制作和调试中总结的“故障排除指南”和一些让项目更酷的扩展思路。
6.1 问题排查速查表
| 现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| OLED屏不亮或乱码 | 1. I2C地址错误 2. 接线松动 3. 库未正确安装 | 1. 扫描I2C地址:上传一个I2C扫描程序,确认屏的地址是0x3C还是0x3D,并修改代码中display.begin()的参数。2. 检查SDA、SCL、VCC、GND四根线是否接牢。 3. 在库管理器中重新安装 Adafruit_SSD1306和Adafruit_GFX库。 |
| 距离读数始终为0或超大 | 1. 传感器接线错误(Trig/Echo反接) 2. 传感器损坏 3. 测量对象吸声或太远 | 1. 核对Trig接数字9,Echo接数字10。 2. 用 NewPing库示例代码ping_cm()单独测试传感器。3. 确保传感器前方有硬质反射面,且距离在2-80cm内。 |
| 计数不准确(漏数或多计) | 1. 阈值设置不合理 2. 传感器安装角度不佳 3. 动作不规范,波动大 | 1.进入校准模式,重新测量高低位距离。 2. 调整传感器角度,使其对准胸口运动轨迹中心。 3. 打开串口绘图器,观察动作曲线,调整 DEBOUNCE_DELAY参数(加大可防抖但会迟滞)。 |
| 蜂鸣器不响 | 1. 未串联限流电阻 2. 正负极接反 3. 引脚定义错误 | 1.必须在蜂鸣器正极和Arduino引脚间串联一个220Ω电阻。 2. 有源蜂鸣器长脚为正,接信号线;短脚为负,接GND。 3. 检查代码中 BUZZER_PIN是否为实际连接的引脚(如8)。 |
| 系统运行不稳定,偶尔重启 | 1. 电源供电不足 2. 接线虚焊或短路 | 1. 换用5V/2A的手机充电器通过DC口给Arduino供电,而非电脑USB。 2. 仔细检查面包板所有连接点,确保无松动或相邻插孔短路。 |
6.2 项目进阶与扩展思路
当基础功能稳定后,你可以尝试以下扩展,让这个计数器变成你的私人健身管家:
数据持久化与历史回顾:
- 添加SD卡模块:将每次锻炼的日期、时间、总次数、组数保存到CSV文件中。之后可以将数据导入电脑用Excel分析趋势。
- 使用EEPROM:将个人最佳记录(单次最多、最快完成10个等)保存在Arduino芯片的EEPROM中,实现掉电保存。
无线化与手机联动:
- 换用ESP8266/ESP32:将主控换成NodeMCU或ESP32开发板。通过Wi-Fi,可以将计数数据实时上传到物联网平台(如Blynk、ThingsBoard),在手机App上创建炫酷的仪表盘。
- 蓝牙传输:添加HC-05蓝牙模块,将数据发送到手机,用MIT App Inventor自己编写一个简单的安卓App来接收和显示数据。
增加运动模式与语音提示:
- 模式切换:增加一个按钮,切换“标准俯卧撑”、“跪姿俯卧撑”、“钻石俯卧撑”等不同模式,每种模式可以有不同的阈值或计数规则。
- 语音反馈:用DFPlayer Mini模块配合一个小喇叭,播报“第一个”、“加油”、“最后一组”等预录的语音,体验更佳。
姿态纠正与数据分析:
- 使用MPU6050传感器:贴在背上,不仅可以计数,还能检测身体是否保持直线(分析俯仰角数据),在OLED上给出“身体下沉!”或“臀部太高!”的实时反馈。
- 计算速度与节奏:通过记录每次“高-低-高”周期的时间,计算平均速度,并提示你是否过快(借力)或过慢。
这个基于Arduino的俯卧撑计数器项目,从想法到实现,贯穿了需求分析、硬件选型、电路设计、算法编程、调试部署的全过程。它最吸引我的地方在于,用非常有限的成本和技术,优雅地解决了一个真实的小问题。过程中,对状态机的理解、对传感器特性的把握、对调试方法的运用,其价值远超项目本身。当你第一次成功让它准确数出“1, 2, 3...”时,那种亲手创造事物并让其按你意志运行的快乐,正是电子制作和编程最大的魅力所在。希望这份详细的指南能帮你少走弯路,顺利做出属于自己的智能健身伙伴。如果在制作中遇到任何新问题,不妨回头看看串口监视器里的数据,那往往是通往答案的钥匙。