一款好玩的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小工具,还有我的渐变色计算表。
能不能发一下pcb原理图,谢谢
不错,多少钱?:lol 电能用多久呢 作业小斗士 发表于 2019-10-7 11:41
能不能发一下pcb原理图,谢谢
这块表可以去github搜一下。 ynkmzyl 发表于 2019-10-7 15:54
不错,多少钱?
可以去某宝某鱼搜一下。好像比较亲民。 coloz 发表于 2019-10-7 21:02
电能用多久呢
ips彩屏和esp32都不是省油的灯,这么袖珍,电池也必然有限。所以穿戴还是看算法,我没有实测过使用时间。如果一个完整的穿戴作品肯定要做省电处理。这块表有姿态传感器以及一个多功能开关。可以有两个思路,一是姿态传感器计算抬手姿态后亮屏。二是多功能开关触碰亮屏。我觉得可以用mpy的多线程试试。 本帖最后由 ynkmzyl 于 2019-10-9 16:45 编辑
问了一下卖家,待机才2天,讲实用性不如M5StickV,讲娱乐性不如手机:lol甚至不如儿童手表:lol讲外观,手环实用性远不如比它便宜一点的小米手环4:lol
ynkmzyl 发表于 2019-10-9 16:44
问了一下卖家,待机才2天,讲实用性不如M5StickV,讲娱乐性不如手机甚至不如儿童手表讲外观,手环实用 ...
除了m5stickv,您列举的都是电子消费品。极客平台和消费品没有可比性(一个是成熟电子产品,一个是自己diy的平台)。而m5stickv是基于k210的,重点在视觉识别。其实是一个视觉识别传感器。除了体积以外与这块极客手表也无任何可比性。见仁见智吧。关于待机,实际上主要在自己的算法设计,如果采用低功耗算法,肯定远远大于两天。这也是折腾的乐趣吧。 沧浪老师的教程学习一下。
页:
[1]
2