一款好玩的esp32极客手表(WatchIO)之BLE心率表-Arduino中文社区 - Powered by Discuz! Archiver

沧海笑1122 发表于 2019-10-6 23:22

一款好玩的esp32极客手表(WatchIO)之BLE心率表

本帖最后由 沧海笑1122 于 2019-10-6 23:30 编辑

一款好玩的esp32极客手表(WatchIO)之BLE心率表

【故事缘由】
    我的好友小华师兄的新作,一款好玩的极客手表(WatchIO),基于esp32的core,是目前我遇到的最袖珍的极客手表。说是极客手表,也就是可编程、可折腾,并且消费品意义的手表。
   一张图我们了解一下这款袖珍的极客手表。
我们看看它的尺寸。对,没错,是31mm*22.6mm,比一个U盘还袖珍。手表到手后,想到还是延续我的智能穿戴想法,就把BLE心率表移植到了这块WatchIO上,先看看小视频,了解一下工作情况吧。 这是锻炼后恢复中的一个心率图形。http://player.youku.com/embed/XNDM4ODM4MzEzNg==
点击→→☞☞☞这里观察小视频
【硬件准备】
序号名称备注
1WatchIO1块
是的,只有WatchIO,不需要别的配件。 【软件准备】
名称备注
1Adafruit_GFX_LibraryAdafruit著名的图形库
2Adafruit_ST7735_and_ST7789_LibraryAdafruitST7735库,在此我们用到的是7735
3ESP32 BLE arduino已经被吸收为官方的esp32 ble库
【代码及注释】/*
===========================================
       Copyright (c) 2018 Stefan Kremser
            github.com/spacehuhn
===========================================
(1)图形部分根据ESP8266 + OLED = WiFi Packet Monitor 修改
(2)BLE蓝牙带部分,根据# ESP32 BLE HRM/MQTT Gateway Code for an ESP32 with an integrated OLED Connecting to a BLE HRM Monitor
   Links:- Video https://youtu.be/iCKIIMrphtg
Date:
2019-08-27
                1、利用watchIO平台,读取ble心率带数据并且显示
                2、后续将心率数据以彩色图形方式显示(160*80)
   2019-10-04
   1、优化显示页面以及图形部分
   2019-10-05
   1、增加了11段渐变色显示心率图形

*/

char c_hrm[]="";//char 类型的hrm值,用于在手表显示器中显示字符数据

//WatchIO手表配置
#include "config.h"
#include "power.h"
#include "lcd.h"
const uint16_t COLORS_LIGHT = {
0xFA55, 0x0C3E, 0xC01E, 0xF255, 0xF820,
0xF321, 0xFFA0, 0x17A0, 0x04BF, 0xC01F
};

const uint32_t COLORS_DARK = {
0x40E6, 0x0128, 0x3809, 0x38C5, 0x4001,
0x40E0, 0x4A20, 0x0220, 0x0108, 0x3008
};

//BLE部分设置
#include "BLEDevice.h"
const String sketchName = "ESP32 HRM BLE Client";
staticBLEUUID serviceUUID(BLEUUID((uint16_t)0x180D));
// The HRM characteristic of the remote service we are interested in.
staticBLEUUID    charUUID(BLEUUID((uint16_t)0x2A37));
static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static boolean notification = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;

unsigned long screen_update, stats_update;

// TypeDef
typedef struct {
char ID;
uint16_t HRM;
}HRM;
HRM hrm;

/*===== GRAPH SETTINGS =====*/
/* Display settings */
#define minRow       0            /* default =   0 */
#define maxRow   87            /* 局部刷新区88列 */
#define minLine      0            /* default =   0 */
#define maxLine    63             /* 局部刷新区64行 */

//* render settings */此部分备用
#define Row1         0
#define Row2      30

//#define LineText   0
//#define Line      60
#define LineVal    60 //按60做比例系数,<最高行数63

//===== Run-Time variables =====//
unsigned long prevTime   = 0;
unsigned long curTime    = 0;
unsigned long hrms       = 0;//全局变量-心率值
unsigned long maxVal   = 0;
double multiplicator   = 0.0;
unsigned int val;//数组size

//计算显示用的比例因子
void getMultiplicator() {
maxVal = 1;
//选出最大值
for (int i = 0; i < maxRow; i++) {
    if (val > maxVal) maxVal = val;
}
//如果maxVal大于LineVal,则比例因子=LineVal/maxVal,否则比例因子=1
if (maxVal > LineVal) multiplicator = (double)LineVal / (double)maxVal;
else multiplicator = 1;
}


//--------------------------------------------------------------------------------------------
// BLE notifyCallback 在此Callback函数中,读取心率带数据,然后给全局变量unsigned long hrms 赋值
//--------------------------------------------------------------------------------------------
static void notifyCallback( BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
    hrm.HRM = pData;
    String hrm_str =String(pData, DEC);   
          hrms=hrm_str.toInt();
}

//--------------------------------------------------------------------------------------------
//Connect to BLE HRM
//--------------------------------------------------------------------------------------------
bool connectToServer(BLEAddress pAddress) {
    Serial.print(F("Forming a connection to "));
    Serial.println(pAddress.toString().c_str());

    BLEClient*pClient= BLEDevice::createClient();
    Serial.println(F(" - Created client"));
   
    // Connect to the HRM BLE Server.
    pClient->connect(pAddress);
    Serial.println(F(" - Connected to server"));

    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print(F("Failed to find our service UUID: "));
      Serial.println(serviceUUID.toString().c_str());
      return false;
    }
    Serial.println(F(" - Found our service"));


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print(F("Failed to find our characteristic UUID: "));
      Serial.println(charUUID.toString().c_str());
      return false;
    }
    Serial.println(F(" - Found our characteristic"));

    // Register for Notify
    pRemoteCharacteristic->registerForNotify(notifyCallback);
}

//--------------------------------------------------------------------------------------------
// Scan for BLE servers and find the first one that advertises the service we are looking for.
//--------------------------------------------------------------------------------------------
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
   * Called for each advertising BLE server.
   */
void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print(F("BLE Advertised Device found: "));
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) {

      //
      Serial.print(F("Found our device!address: "));
      advertisedDevice.getScan()->stop();

      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
      doConnect = true;

    } // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks




//===== SETUP =====
void setup() {
/* start Serial */
Serial.begin(115200);
Serial.println("starting!");

//Start BLE--BLE部分初始化
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device.Specify that we want active scanning and start the
// scan to run for 30 seconds.
BLEDevice::init("");
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(true);
pBLEScan->start(30);
//-----watchIO 部分初始化
init_power();
lcd_init();

}

//===== LOOP =====//
void loop() {
curTime = millis();//当前时间计时
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect.Now we connect to it.Once we are
// connected we set the connected flag to be true.
if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println(F("We are now connected to the BLE HRM"));
      connected = true;
    } else {
      Serial.println(F("We have failed to connect to the HRM; there is nothin more we will do."));
    }
    doConnect = false;
}
//every 3 second
if (curTime - prevTime >= 3000) {
          // Turn notification on
if (connected) {
    if (notification == false) {
      Serial.println(F("Turning Notifocation On"));
      const uint8_t onPacket[] = {0x1, 0x0};
      pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)onPacket, 2, true);
      notification = true;
    }
}
    prevTime = curTime;
    char c_hrm[]="";
          dtostrf(hrms,3,0,c_hrm); //转换为char类型,用于显示
    //整体左移所有的心率值
    for (int i = 0; i < maxRow; i++) {
      val = val;
    }
    val = hrms;//最新数据放入val

    //recalculate scaling factor 重新计算比例因子
    getMultiplicator();
    //调试信息,运行时可以去除
   Serial.print("hrm:");
   Serial.println(hrms);   //串口打印一个心率值
//canvas.fillScreen(ST77XX_BLUE);
canvas.fillScreen(0x435c);//屏幕背景色
canvas.setCursor(10, 10);
canvas.setTextSize(2);
canvas.setTextColor(ST77XX_WHITE);
   canvas.print("HRM:"); //显示HRM标签
   canvas.setTextSize(3);
canvas.setTextColor(ST77XX_YELLOW);
   canvas.setCursor(10, 50);
   canvas.print(hrms);//显示心率数据
      //画一个矩形填充框,图像在其中显示
      canvas.fillRect(60,4,94,71,0xFF79);
//============在矩形填充框内绘制心率图形
    for (int i = 0; i <=maxRow; i++) {
   // 用垂直线表达心率值,并且按照比例尺进行调整
   //调试信息,运行时请去除
   //Serial.println("i--------   val ---------      multiplicator");
   //Serial.println(i);
    //Serial.println(val);
    // Serial.println(multiplicator);
      int line3;//最初是三段式渐变,因此命名为Line3,后面延用
      line3= val*multiplicator;
      //以下我们准备了三个方案显示心率图形
      /*方案一:三段显示
   // 第一段:0~33%0xEFDE浅
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.66, line3*0.34, 0xEFDE);
   // 第二段:34~66%0x76B8中
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.33, line3*0.33, 0x76B8);
      //第三段:67~100%0x1531深
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9,line3*0.33, 0x1531 );
      */
      //------------方案二,四段显示
      /*
   //第一段:0~33%0xff79浅
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.55, line3*0.45, 0xff79);
   // 第二段:34~66%0xfef9中
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.25, line3*0.3, 0xfef9);
      //第三段:67~100%0xfe79较深
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.05,line3*0.2, 0xfe79 );
      //第四段:61~100%0xfdd9深
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9,line3*0.05, 0x7840 );
      */
//----------------方案三,11段,第一段和最后一段各5%,剩余10%每段
         //第一段:0~5% 0xef60 浅
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.94, line3*0.05+1, 0xef60);
   // 第二段:6~15%0xf6e0
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.84, line3*0.1+1, 0xf6e0);
      //第三段:16~25% 0xf660
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.74, line3*0.1+1, 0xf660);
      //第四段:26~35%0xf5c0
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.64, line3*0.1+1, 0xf5c0);
      //第五段:36~45% 0xf540
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.54, line3*0.1+1, 0xf540);
   // 第六段:46~55%0xf4c0
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.44, line3*0.1+1,0xf4c0);
      //第七段:56~65%0xfc40
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.34,line3*0.1+1,0xfc40);
      //第八段:66~75%0xfbc0
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.24,line3*0.1+1,0xfbc0);
      //第九段:76~85% 0xfb20
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.14, line3*0.1+1,0xfb20);
   // 第十段:86~95%0xfaa0
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9+line3*0.05, line3*0.1+1,0xfaa0);
   // 第十一段:96~100%0xfa20 深
      canvas.drawFastVLine(i+63, int(maxLine - val*multiplicator)+9, line3*0.05,0xfa20); //红色
            
   }
    hrms       = 0;
    delay(500);

}
}
【关于渐变色的心率图形展示】   首先我们看看成品的心率图形显示,利用11段渐变色进行展示,本文的示例使用的是暖色调,也就是从橘红色到黄色的一个渐变处理。一、渐变色处理的原理以及公式https://www.cnblogs.com/Free-Thinker/p/5569781.html
Gradient = A + (B-A) / Step * N说明:Gradient 是总数为step的分段数中第N段渐变色数值A是目标值,B是起始值,step是总分段数,N是第n段数据
二、分步实现渐变色的计算和变换Step1:设定起始颜色以及目标颜色本例中我们设定了橘红色(255-69-0)作为起始值,黄色2(238-238-0)作为渐变得目标值。Step2:计算分段渐变色的RGB数值,并且转换为24位颜色数据Step3:将RGB888转换为RGB5651、888和565的区别https://blog.csdn.net/ctthuangcheng/article/details/8551559【引用此贴的描述】正常的RGB24是由24位即3个字节来描述一个像素,R、G、B各8位。而实际使用中为了减少图像数据的尺寸,如视频领域,对R、G、B所使用的位数进行的缩减,如你所说的RGB565和RGB555。RGB565 就是R-5bit,G-6bit,B-5bitRGB888 就是R-8bit,G-8bit,B-8bit ;其实这就是RGB24具体来说:RGB565 是16位的,2个字节,5+6+5,第一字节的前5位是R,后三位+第二字节前三位是G,第二字节后5位是B。RGB888 是24位的,3个字节。2、转换小程序因为在WatchIO使用到的屏幕以及Adafruit_GFX_Library支持16位的RGB565数据。所以我们需要做一个888-565的转换。我们不需要造轮子了,在网上找到了这样的一个实用小工具,我将其附在帖子的附件里。
完成上述步骤后,我们就得到了一组渐变色的RGB565数据,实际运行后,得到了一个类似火焰的暖色调渐变色心率数据。
【小结】    这是一块很好玩的极客手表,我只是将esp32的BLE心率带做了一个简单的移植,希望起到一个抛砖引玉的作用,使得更多的玩家关注这块手表,关注小华师兄等极客在袖珍的智能穿戴之路上的探索。
以下附件附上源代码、必要的库以及888-565小工具,还有我的渐变色计算表。


作业小斗士 发表于 2019-10-7 11:41

能不能发一下pcb原理图,谢谢

ynkmzyl 发表于 2019-10-7 15:54

不错,多少钱?:lol

coloz 发表于 2019-10-7 21:02

电能用多久呢

沧海笑1122 发表于 2019-10-8 12:03

作业小斗士 发表于 2019-10-7 11:41
能不能发一下pcb原理图,谢谢

这块表可以去github搜一下。

沧海笑1122 发表于 2019-10-8 12:03

ynkmzyl 发表于 2019-10-7 15:54
不错,多少钱?

可以去某宝某鱼搜一下。好像比较亲民。

沧海笑1122 发表于 2019-10-8 12:07

coloz 发表于 2019-10-7 21:02
电能用多久呢

ips彩屏和esp32都不是省油的灯,这么袖珍,电池也必然有限。所以穿戴还是看算法,我没有实测过使用时间。如果一个完整的穿戴作品肯定要做省电处理。这块表有姿态传感器以及一个多功能开关。可以有两个思路,一是姿态传感器计算抬手姿态后亮屏。二是多功能开关触碰亮屏。我觉得可以用mpy的多线程试试。

ynkmzyl 发表于 2019-10-9 16:44

本帖最后由 ynkmzyl 于 2019-10-9 16:45 编辑

问了一下卖家,待机才2天,讲实用性不如M5StickV,讲娱乐性不如手机:lol甚至不如儿童手表:lol讲外观,手环实用性远不如比它便宜一点的小米手环4:lol

沧海笑1122 发表于 2019-10-10 09:38

ynkmzyl 发表于 2019-10-9 16:44
问了一下卖家,待机才2天,讲实用性不如M5StickV,讲娱乐性不如手机甚至不如儿童手表讲外观,手环实用 ...

除了m5stickv,您列举的都是电子消费品。极客平台和消费品没有可比性(一个是成熟电子产品,一个是自己diy的平台)。而m5stickv是基于k210的,重点在视觉识别。其实是一个视觉识别传感器。除了体积以外与这块极客手表也无任何可比性。见仁见智吧。关于待机,实际上主要在自己的算法设计,如果采用低功耗算法,肯定远远大于两天。这也是折腾的乐趣吧。

topdog 发表于 2019-10-10 20:01

沧浪老师的教程学习一下。
页: [1] 2
查看完整版本: 一款好玩的esp32极客手表(WatchIO)之BLE心率表