21浏览
查看: 21|回复: 2

[项目] 【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴

[复制链接]
认识 Piko — 您的小巧像素化健身伴侣,它住在您的手腕上,全天为您加油。他不仅可爱——他反应灵敏、反应灵敏、充满个性!
Piko 可以实时检测您的活动,无论您是休息、步行、慢跑还是短跑。每次你移动,他也会移动。他会在您身边蹦蹦跳跳、行进或喧嚣 - 将您的脚步变成动画。但不要太懒......如果 Piko 没有达到他的每日步数目标,他就会关闭。如果他睡着了,那么......他可能不会再醒来。

用品:
• 烙铁
• 焊锡丝
• 剥线钳
• 电线 (公 - 公)
• 万用表(带有连续性测试仪的手表)
• 热胶枪
• 热胶枪棒
组件:
• LCD 显示屏
• ESP32 甲壳虫 C6
• 加速度计
• 200mAh 锂电池
• 3 针 SPDT 开关
• 一条表带(我用的是我旧破手表中的一条)
在零件集成方面,来自 DFRobot 的组件运行得非常好

Piko 使用板载加速度计跟踪您的运动,应用一些巧妙的数学和物理学来估计您走步的速度和频率。我们稍后将深入探讨技术细节,但简而言之,通过分析加速度的变化,Piko 可以弄清楚你在做什么。

一旦他检测到您的动作发生变化,例如从步行切换到跑步,ESP32 就会启动,选择正确的动画来匹配您的活动。这就是您了解 Piko 跟上步伐的方式:他的显示会随着您的变化而变化。

Piko 的设计灵感来自 Tamagotchi 角色的怀旧魅力和极简主义像素艺术的简洁美学。目标是创造一个让人感到熟悉、舒适且视觉永恒的伴侣。
他被有意设计为:
• 瞬间可爱,带有一丝面无表情的个性
• 平易近人且不令人生畏
• 视觉简单,仅使用黑色和白色,以获得最大的清晰度和魅力

当您走路、跑步或冲刺时,您是在推动地面向前移动。这种推力会产生一种力,这会导致你的速度发生变化——在物理学中,速度的变化被称为......你猜对了:加速。
这个想法是这样的:
• 使用加速度计测量加速度变化
• 过滤和处理原始数据以减少噪音
• 将数据与空闲、步行、慢跑、冲刺和睡眠模式的预定义阈值进行比较。
• 根据结果,更新 Piko 的动画并计算所走的步数,这显示在 Piko 正下方的进度条上
这是一个简单的循环,但它让一切变得不同,将您的现实世界的动作变成 Piko 可以理解和响应的事物。

需要注意的主要部分是我的思想和身体深深交织在一起,重要的是像我的显示这样的功能不要打断我的思考,否则我可能会在不该睡着的时候睡着,或者在你完成慢跑后开始跑步。
像这样将所有文件放在一个干净的文件夹中也很重要,这样当您在 Arduino IDE 中单击编译和上传时,我大脑的每个部分都知道其他部分在哪里。
此外,您需要在 Arduino IDE 工具下拉菜单中启用 USB CDC-On-Boot,然后才能将我的 Spirit(固件)连接到我的大脑(硬件)。
完成此作,并安装并下载了您的所有库,我应该会思考、看起来和感觉非常棒。
(关于软件:所有基于 GIF 的库都是 Arduino IDE 原生的,可以通过库管理器找到,而加速度计和滤波器库可以从 GitHub 的加速步骤 :) 的评论中找到)

构建和编码所有内容后,是时候在 Piko 的实际应用中进行测试了。试着轻轻摇晃他或将他绑在你的手腕上四处走动。如果一切都正确连接和编码,他应该切换动画状态以反映您的动作。

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图1

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图3【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图2

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图8

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图9

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图10

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图11

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图12

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图13

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图14

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图15

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图16

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图4

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图6

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图5

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴图7

驴友花雕  中级技神
 楼主|

发表于 昨天 17:23

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴

项目代码

  1. #include <FiltersFromGit.h>
  2. #include <DFRobot_LIS.h>
  3. #include "PikoAccelerate.h"
  4. #include <SPI.h>
  5. #include <Adafruit_GFX.h>
  6. #include <Adafruit_ST7789.h>
  7. #include <AnimatedGIF.h>
  8. #include "piko_sleep.h"
  9. #include "piko_idle.h"  // Replace with your actual .h gif files
  10. #include "piko_walk.h"
  11. #include "piko_jog.h"
  12. #include "piko_sprint.h"
  13. // Define your MACROS for the LCD
  14. #define TFT_CS     5
  15. #define TFT_RST    6
  16. #define TFT_DC     7
  17. #define SLEEP_THRESHOLD 10000
  18. //Function declarations:
  19. void GIFDraw(GIFDRAW *pDraw); //Displays the GIF on the LCD
  20. void accelerationJob(void); //manages all acceleration absed activities
  21. void drawProgressBar(int steps) ;//manages the loading bar based of steps
  22. //Object initilisations
  23. DFRobot_LIS331HH_I2C acce(&Wire, I2C_ACCE_ADDRESS); //creates an accelerometer object that communicates via I2C
  24. FilterOnePole myAccelerationFilter(LOWPASS, fc); //creates the filter object for accelerometer data
  25. RunningStatistics myAccelerationStats;//creates an object that continously monitors acceleration mean and std
  26. Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
  27. AnimatedGIF gif;
  28. //Global vars
  29. char* overlayText = "0";
  30. unsigned long lastSampleTime = 0;
  31. unsigned long sampleRate = 20; //ensures samples every ~20ms
  32. MotionState previousState = NONE; //ensures that the first GIF will run
  33. unsigned long lastFrameTime = 0;
  34. int frameDelay = 0; //DO NOT CHANGE unknowingly. Ensures playfram function that draws GIF is non-blocking
  35. int FPS = 9; //Desired frame rate
  36. unsigned long sleeptimeCounter = 0;
  37. unsigned long lastsleepcheckTime = 0;
  38. bool gifPlaying = false;
  39. // Data arrays (replace with your actual GIF names)
  40. // THESE MUST BE IN THIS ORDER, since indexed by motionType
  41. const uint8_t* gifData[] = { idle_v2, walk_v2, jog_v2, sprint_v2, sleep_v2};
  42. size_t gifSize[] = { sizeof(idle_v2), sizeof(walk_v2), sizeof(jog_v2),sizeof(sprint_v2), sizeof(sleep_v2)};
  43. const int MAX_STEPS = 200; //Number of steps to fill the progress bar
  44. void setup() {
  45.   //Serial set
  46.   Serial.begin(115200);
  47.   while(!Serial){};
  48.   while(!acce.begin()){
  49.     Serial.println("Initialization failed, please check the connection and I2C address - must be");
  50.   }
  51.   //take statistics averages/std's set-up
  52.   myAccelerationStats.setWindowSecs(WINDOW);
  53.   motionType = idling;
  54.   //accelerometer set up
  55.   Serial.print("chip id : ");
  56.   Serial.println(acce.getID(),HEX);
  57.   acce.setRange(/*range = */DFRobot_LIS::eLis331hh_12g);
  58.   acce.setAcquireRate(/*rate = */DFRobot_LIS::eNormal_50HZ);
  59.     // Initialize display
  60.   tft.init(240, 240);  // Use your screen resolution
  61.   tft.setRotation(2);  // Adjust rotation if needed
  62.   tft.fillScreen(ST77XX_BLACK);
  63.   tft.setTextColor(ST77XX_WHITE);       // Choose your text color
  64.   tft.setTextSize(2);                   // Adjust as needed
  65.   tft.setCursor(10, 10);                // X, Y position
  66.   tft.invertDisplay(false);
  67.   // Initialize GIF decoder
  68.   gif.begin();  // No endian flag needed for Adafruit library
  69. }
  70. void loop() {
  71.   unsigned long now = millis();
  72.   //Update state every 20ms
  73.   if (now - lastSampleTime >= sampleRate) {
  74.     lastSampleTime = now;
  75.     accelerationJob();
  76.   }
  77.   //Handles if it needs to go into a sleep state.
  78.   if(motionType == idling){
  79.     sleeptimeCounter = sleeptimeCounter+now-lastsleepcheckTime;
  80.     if(sleeptimeCounter>=SLEEP_THRESHOLD){
  81.       motionType=sleeping;
  82.     }
  83.     lastsleepcheckTime = now;
  84.   }
  85.   else{
  86.     sleeptimeCounter=0;
  87.     lastsleepcheckTime = now;
  88.   }
  89.   // If state changed, open new GIF
  90.   if (motionType != previousState) {
  91.     gif.close(); // Close previous GIF
  92.     if (gif.open((uint8_t*)gifData[motionType], gifSize[motionType], GIFDraw)) {
  93.       gifPlaying = true;
  94.       lastFrameTime = now;
  95.       frameDelay = 0;
  96.       previousState = motionType;
  97.     } else {
  98.       Serial.println("Failed to open GIF");
  99.       gifPlaying = false;
  100.     }
  101.   }
  102.   // 3. Non-blocking GIF frame playback
  103.   if (gifPlaying && now - lastFrameTime >= 1/FPS) {
  104.     int result = gif.playFrame(false, &frameDelay);
  105.     lastFrameTime = now;
  106.     drawProgressBar(steps);
  107.     if (result == 0) {
  108.       gif.reset();  // Or gifPlaying = false if you don't want to loop
  109.     }
  110.   }
  111. }
  112. /********************************************************************************************************************/
  113. /************************************************Function Definitions************************************************/
  114. /********************************************************************************************************************/
  115. void accelerationJob(void){
  116.   
  117.   //Acceleration Raw Data
  118.   ax = acce.readAccX();
  119.   ay = acce.readAccY();
  120.   az = acce.readAccZ();
  121.   a = getMagnitude(ax,ay,az)-1000;
  122.   //Filters through Lowpass to remove noise
  123.   myAccelerationFilter.input(a);
  124.   afiltered = myAccelerationFilter.output();
  125.   //Get running statistics
  126.   myAccelerationStats.input(afiltered);
  127.   a_ave = myAccelerationStats.mean();
  128.   a_std = myAccelerationStats.sigma();
  129.   
  130.   //Acceleration Logic
  131.   motionType = determineMovementType(a_ave, a_std);
  132.   countSteps(afiltered, motionType);
  133. }
  134. void GIFDraw(GIFDRAW *pDraw) {
  135.   if (pDraw->y >= tft.height()-37) return; //-37 ensures gif doesn't overdraw on the loading bar
  136.   static uint16_t lineBuffer[320];  // Enough for full width
  137.   uint8_t *s = pDraw->pPixels;
  138.   uint8_t *pal = (uint8_t *)pDraw->pPalette;
  139.   for (int x = 0; x < pDraw->iWidth; x++) {
  140.     if (pDraw->ucHasTransparency && *s == pDraw->ucTransparent) {
  141.       lineBuffer[x] = tft.color565(0, 0, 0);  // Optional: treat as black
  142.       s++;
  143.       continue;
  144.     }
  145.     uint8_t index = *s++;
  146.     lineBuffer[x] = tft.color565(pal[index * 3], pal[index * 3 + 1], pal[index * 3 + 2]);
  147.   }
  148.   tft.drawRGBBitmap(pDraw->iX, pDraw->iY + pDraw->y, lineBuffer, pDraw->iWidth, 1);
  149.   if (pDraw->y == (pDraw->iHeight - 1)) {
  150.     tft.setTextColor(ST77XX_WHITE, ST77XX_WHITE); // Optional: erase previous text background
  151.     tft.setTextSize(2);
  152.     tft.setCursor(10, 10);
  153.     tft.print(String(steps));
  154.   }
  155. }
  156. void drawProgressBar(int steps) {
  157.   Serial.println("I am in draw bar fn");
  158.   static int lastFillWidth = -1; // remember the last fill width (ensure static)
  159.   int barWidth = 160;
  160.   int barHeight = 18;
  161.   int thickness = 2;
  162.   int bottomPadding = 15;
  163.   int x = (tft.width() - barWidth) / 2;
  164.   int y = tft.height() - barHeight - bottomPadding;
  165.   uint16_t barColor = tft.color565(216, 217, 217);
  166.   int clampedsteps = constrain(steps,0,MAX_STEPS);
  167.   int fillInset = thickness;
  168.   int fillWidth = map(clampedsteps, 0, MAX_STEPS, 0, barWidth - 2 * fillInset);
  169.   // Only redraw if the fill width changed -better speed
  170.   if (fillWidth == lastFillWidth) return;
  171.   lastFillWidth = fillWidth;
  172.   // Draw thicker outline via multiple rectangles
  173.   for (int i = 0; i < thickness; i++) {
  174.     tft.drawRect(x - i, y - i, barWidth + 2 * i, barHeight + 2 * i, barColor);
  175.   }
  176.   // Clear previous fill area
  177.   tft.fillRect(x + fillInset, y + fillInset, barWidth - 2 * fillInset, barHeight - 2 * fillInset, ST77XX_BLACK);
  178.   // Draw current fill
  179.   tft.fillRect(x + fillInset, y + fillInset, fillWidth, barHeight - 2 * fillInset, barColor);
  180. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 昨天 17:29

【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail