【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:反应灵敏、充满个性的健身好伙伴
项目代码#include <FiltersFromGit.h>
#include <DFRobot_LIS.h>
#include "PikoAccelerate.h"
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <AnimatedGIF.h>
#include "piko_sleep.h"
#include "piko_idle.h"// Replace with your actual .h gif files
#include "piko_walk.h"
#include "piko_jog.h"
#include "piko_sprint.h"
// Define your MACROS for the LCD
#define TFT_CS 5
#define TFT_RST 6
#define TFT_DC 7
#define SLEEP_THRESHOLD 10000
//Function declarations:
void GIFDraw(GIFDRAW *pDraw); //Displays the GIF on the LCD
void accelerationJob(void); //manages all acceleration absed activities
void drawProgressBar(int steps) ;//manages the loading bar based of steps
//Object initilisations
DFRobot_LIS331HH_I2C acce(&Wire, I2C_ACCE_ADDRESS); //creates an accelerometer object that communicates via I2C
FilterOnePole myAccelerationFilter(LOWPASS, fc); //creates the filter object for accelerometer data
RunningStatistics myAccelerationStats;//creates an object that continously monitors acceleration mean and std
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
AnimatedGIF gif;
//Global vars
char* overlayText = "0";
unsigned long lastSampleTime = 0;
unsigned long sampleRate = 20; //ensures samples every ~20ms
MotionState previousState = NONE; //ensures that the first GIF will run
unsigned long lastFrameTime = 0;
int frameDelay = 0; //DO NOT CHANGE unknowingly. Ensures playfram function that draws GIF is non-blocking
int FPS = 9; //Desired frame rate
unsigned long sleeptimeCounter = 0;
unsigned long lastsleepcheckTime = 0;
bool gifPlaying = false;
// Data arrays (replace with your actual GIF names)
// THESE MUST BE IN THIS ORDER, since indexed by motionType
const uint8_t* gifData[] = { idle_v2, walk_v2, jog_v2, sprint_v2, sleep_v2};
size_t gifSize[] = { sizeof(idle_v2), sizeof(walk_v2), sizeof(jog_v2),sizeof(sprint_v2), sizeof(sleep_v2)};
const int MAX_STEPS = 200; //Number of steps to fill the progress bar
void setup() {
//Serial set
Serial.begin(115200);
while(!Serial){};
while(!acce.begin()){
Serial.println("Initialization failed, please check the connection and I2C address - must be");
}
//take statistics averages/std's set-up
myAccelerationStats.setWindowSecs(WINDOW);
motionType = idling;
//accelerometer set up
Serial.print("chip id : ");
Serial.println(acce.getID(),HEX);
acce.setRange(/*range = */DFRobot_LIS::eLis331hh_12g);
acce.setAcquireRate(/*rate = */DFRobot_LIS::eNormal_50HZ);
// Initialize display
tft.init(240, 240);// Use your screen resolution
tft.setRotation(2);// Adjust rotation if needed
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE); // Choose your text color
tft.setTextSize(2); // Adjust as needed
tft.setCursor(10, 10); // X, Y position
tft.invertDisplay(false);
// Initialize GIF decoder
gif.begin();// No endian flag needed for Adafruit library
}
void loop() {
unsigned long now = millis();
//Update state every 20ms
if (now - lastSampleTime >= sampleRate) {
lastSampleTime = now;
accelerationJob();
}
//Handles if it needs to go into a sleep state.
if(motionType == idling){
sleeptimeCounter = sleeptimeCounter+now-lastsleepcheckTime;
if(sleeptimeCounter>=SLEEP_THRESHOLD){
motionType=sleeping;
}
lastsleepcheckTime = now;
}
else{
sleeptimeCounter=0;
lastsleepcheckTime = now;
}
// If state changed, open new GIF
if (motionType != previousState) {
gif.close(); // Close previous GIF
if (gif.open((uint8_t*)gifData, gifSize, GIFDraw)) {
gifPlaying = true;
lastFrameTime = now;
frameDelay = 0;
previousState = motionType;
} else {
Serial.println("Failed to open GIF");
gifPlaying = false;
}
}
// 3. Non-blocking GIF frame playback
if (gifPlaying && now - lastFrameTime >= 1/FPS) {
int result = gif.playFrame(false, &frameDelay);
lastFrameTime = now;
drawProgressBar(steps);
if (result == 0) {
gif.reset();// Or gifPlaying = false if you don't want to loop
}
}
}
/********************************************************************************************************************/
/************************************************Function Definitions************************************************/
/********************************************************************************************************************/
void accelerationJob(void){
//Acceleration Raw Data
ax = acce.readAccX();
ay = acce.readAccY();
az = acce.readAccZ();
a = getMagnitude(ax,ay,az)-1000;
//Filters through Lowpass to remove noise
myAccelerationFilter.input(a);
afiltered = myAccelerationFilter.output();
//Get running statistics
myAccelerationStats.input(afiltered);
a_ave = myAccelerationStats.mean();
a_std = myAccelerationStats.sigma();
//Acceleration Logic
motionType = determineMovementType(a_ave, a_std);
countSteps(afiltered, motionType);
}
void GIFDraw(GIFDRAW *pDraw) {
if (pDraw->y >= tft.height()-37) return; //-37 ensures gif doesn't overdraw on the loading bar
static uint16_t lineBuffer;// Enough for full width
uint8_t *s = pDraw->pPixels;
uint8_t *pal = (uint8_t *)pDraw->pPalette;
for (int x = 0; x < pDraw->iWidth; x++) {
if (pDraw->ucHasTransparency && *s == pDraw->ucTransparent) {
lineBuffer = tft.color565(0, 0, 0);// Optional: treat as black
s++;
continue;
}
uint8_t index = *s++;
lineBuffer = tft.color565(pal, pal, pal);
}
tft.drawRGBBitmap(pDraw->iX, pDraw->iY + pDraw->y, lineBuffer, pDraw->iWidth, 1);
if (pDraw->y == (pDraw->iHeight - 1)) {
tft.setTextColor(ST77XX_WHITE, ST77XX_WHITE); // Optional: erase previous text background
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.print(String(steps));
}
}
void drawProgressBar(int steps) {
Serial.println("I am in draw bar fn");
static int lastFillWidth = -1; // remember the last fill width (ensure static)
int barWidth = 160;
int barHeight = 18;
int thickness = 2;
int bottomPadding = 15;
int x = (tft.width() - barWidth) / 2;
int y = tft.height() - barHeight - bottomPadding;
uint16_t barColor = tft.color565(216, 217, 217);
int clampedsteps = constrain(steps,0,MAX_STEPS);
int fillInset = thickness;
int fillWidth = map(clampedsteps, 0, MAX_STEPS, 0, barWidth - 2 * fillInset);
// Only redraw if the fill width changed -better speed
if (fillWidth == lastFillWidth) return;
lastFillWidth = fillWidth;
// Draw thicker outline via multiple rectangles
for (int i = 0; i < thickness; i++) {
tft.drawRect(x - i, y - i, barWidth + 2 * i, barHeight + 2 * i, barColor);
}
// Clear previous fill area
tft.fillRect(x + fillInset, y + fillInset, barWidth - 2 * fillInset, barHeight - 2 * fillInset, ST77XX_BLACK);
// Draw current fill
tft.fillRect(x + fillInset, y + fillInset, fillWidth, barHeight - 2 * fillInset, barColor);
}
【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴
【Arduino 动手做】Piko:反应灵敏、充满个性的健身好伙伴项目链接:https://www.instructables.com/Piko-Your-Friendly-Fitness-Buddy/
项目作者:Iloke Alusala
项目视频:https://www.youtube.com/watch?v=1BNUNgcb1k0
项目代码:https://github.com/Iloke-Alusala/ESP_Piko_Firmware
3D打印文件:
https://content.instructables.com/FNC/0BZV/MB3O60DR/FNC0BZVMB3O60DR.step
https://content.instructables.com/FCC/WXHM/MB3O60EF/FCCWXHMMB3O60EF.step
页:
[1]