查看: 3571|回复: 11

[项目] BLE胸带+GPS墨水屏运动心率表

[复制链接]
  • TA的每日心情
    开心
    2019-7-17 13:13
  • 签到天数: 264 天

    [LV.8]以坛为家I

    发表于 2019-4-15 22:41 | 显示全部楼层 |阅读模式
    本帖最后由 沧海笑1122 于 2019-5-7 00:57 编辑

    项目简介:
        我喜欢跑步(已经坚持了四年),也很喜欢折腾穿戴,两者都喜欢。 2016年参赛的就是一款心率表,当时也是第一次学习 3d建模(使用草图大师),第一次学习画pcb板(sprint layout 6.0)。但是做出来的效果还是让人忍俊不禁。毕竟是起步吧。

    IMG_20190507_005211.jpg

    smartwatch1.jpg
    【简要回顾前两个版本】
    第一版图片
    改进版图片

    时光流逝,这次是一块基于 esp32的蓝牙心率+ GPS手表。以数据和图形方式显示运动心率,同时在电子墨水屏上显示实时运动速度,本次运动距离以及实时搜星的情况。
    主要特点:
    1,微雪 1.54寸单色墨水屏
    2,gps模块采用分离式布置,与手表分离,用HC-08透传方式把数据送下来。
    这个设计的主要原因,是苦于前几个版本将全球定位系统模块置入手表内部,结果搜星效果很不理想,不稳定。有时候戴着出去跑了一两公里,搜星结果还没出来,用户体验非常不好。这次的分离设计,缺点就是另外准备了一套GPS模块+供电系统+ HC-08(BLE透传模块),优点呢,可以吧这个模块放在帽子上,戴在另一只手腕上,或者,开车的时候,放在汽车仪表板前,这样兼顾了穿戴的小巧以及搜星的可靠
    .3,心率传感器采用 BLE心率胸带,标准的 UUID以及BLE心率服务
    3,采用了 M5的手表套件底板(带1000mAh的锂电池),大是大了点(54 *54毫米)但是整体手表高度降下来了,仅为15毫米。
    下面首先看看成品的工作状态吧。




    【基本原理图】

    Schematic_smart-watch-gps-hrm_schematic_20190421221805.png
    【硬件清单】 GPS端
    序号
    内容
    备注
    1
    ATGM332D
    中科微GPS +北斗
    2
    HC-08
    B le 4.0透传模块,汇承
    3
    锂电池充电模块+锂电池
    800mAh的
    4
    DC-DC升压模块
    5V输出
    5V-3.3V降压模块
    6
    拨动开关
    7
    外壳
    一套,3D打印
    手表端

    序号
    内容
    备注
    1
    E sp32
    含锂电池充电管理
    2
    HC-08
    B le 4.0透传模块,汇承
    3
    锂电池
    1000mAh的
    4
    拨动开关
    1
    电子墨水屏
    1.54寸,微雪,单色
    6
    外壳
    一套,3D打印
    【软件库简要介绍】
    TinyGPS ++ 库( gps解析)
    Esp32 A rduino ble库(ble心率带解析)
    微雪 epd1in54 (单色 1.54寸电子墨水屏驱动库)
    原理示意图.PNG

    【GPS模块外壳】 autodest inventor 2019建模(还在初学中,inventor 用机械制图的思维,工科生比较容易上手,而且,能够显示零件之间的装配关系,这一点非常实用方便)
    gps.PNG
    gps1.PNG

    【手表外壳】



    watch1.PNG

    watch0420.PNG


    【代码以及注释】

    [mw_shl_code=arduino,true]/*
    //------------------------------------------------------------------------------
    //  date:2019-01-24
    // 测试gps--hc08-------hc08---esp32
    //
    //测试结果:1、用epaper显示:速度f_speed以及累计运动距离my_dist----OK
    //                    2、今天测试与ble心率带对接,speed\distance已经ok,今天加一个sate数量---OK
    // 引用说明:
    //  (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
    //------------------------------------------------------------------------------
    */
    //---------gps部分设置
    #include <TinyGPS++.h>                 // Tiny GPS Plus Library
    HardwareSerial Serial3(2);             //使用serial2,默认serial2的 RXD2= 16,TXD2= 17
    TinyGPSPlus gps;                      // Create an Instance of the TinyGPS++ object called gps
    //定义gps测距变量
    //上一次经纬度、本次经纬度、速度、可用卫星数
    float lst_lat, lst_lng, f_lat, f_lng, f_speed, f_sate;
    double my_dist; //本次测距的距离
    /*epaper库设置*/
    #include <SPI.h>
    #include <epd1in54.h>
    #include <epdpaint.h>
    #include "imagedata.h"
    #define COLORED     0
    #define UNCOLORED   1
    unsigned char image[5006];
    Paint paint(image, 0, 0);    //  定义局部刷新块paint,显示gps-speed
    Epd epd;//声明一个实例

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

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

    /*===== SETTINGS =====*/
    /* Display settings */
    #define minRow       0            
    #define maxRow     138              
    #define minLine      0              
    #define maxLine    103            
    //#define LineText     0
    //#define Line        60
    #define LineVal     100

    //===== 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[139];

    //计算显示用的比例因子
    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[1];
        String hrm_str =  String(pData[1], 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 =====
    游客,如果您要查看本帖隐藏内容请回复

      epd.ClearFrameMemory(0xFF);   // bit set = white, bit reset = black
      epd.DisplayFrame();
      epd.ClearFrameMemory(0xFF);   // bit set = white, bit reset = black
      epd.DisplayFrame();
      delay(1000);
        if (epd.Init(lut_partial_update) != 0) {
          Serial.print("e-Paper init failed");
          return;
      }
      //显示底图
      epd.SetFrameMemory(IMAGE_DATA);
      epd.DisplayFrame();
      epd.SetFrameMemory(IMAGE_DATA);
      epd.DisplayFrame();

      //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);
    }

    //===== LOOP =====//
    void loop() {
      //==============gps部分数据获取
      f_lat = gps.location.lat();
      f_lng = gps.location.lng();
      f_speed = gps.speed.kmph();
      f_sate = gps.satellites.value();
      //---------计算速度
      char s1[10];
      dtostrf(f_speed, 2, 1, s1);
      Serial.println(s1);
      //--------计算距离
      if (lst_lat == 0) {
        my_dist = my_dist;
      }
      else
      {
        my_dist = my_dist + gps.distanceBetween(f_lat, f_lng, lst_lat, lst_lng) / 1000;
      }
      lst_lat = f_lat;
      lst_lng = f_lng;
      char s2[10] ;
      dtostrf(my_dist, 2, 2, s2);
      Serial.println(my_dist);
    //--------获取sate数量并转换
      char s3[10];
      dtostrf(f_sate, 2, 0, s3);
      Serial.println(s3);
      
      //---------BLE部分
      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[3];
        dtostrf(hrms,3,0,c_hrm); //转换为char类型,用于在epaper显示
        //整体左移所有的心率值
        for (int i = 0; i < maxRow; i++) {
          val = val[i + 1];
         //Serial.println(val);   //串口打印一个心率值
        }
        val[138] = hrms;//最新数据放入val[138]
       //Serial.println(val[138]);
       
        //recalculate scaling factor 重新计算比例因子
        getMultiplicator();
      //------------------epaper部分
      //-----------------通用设置
      paint.SetWidth(20);//paint是心率数据区域
      paint.SetHeight(56);
      paint.SetRotate(ROTATE_270);
      //第一步:显示心率
      paint.Clear(UNCOLORED);
      paint.DrawStringAt(0, 0, c_hrm, &Font24, COLORED);//这是字符在局部刷新块里面的位置
      epd.SetFrameMemory(paint.GetImage(), 135, 150, paint.GetWidth(), paint.GetHeight());
      //-----------显示卫星数
       paint.SetWidth(18);//paint是卫星数据区域
      paint.SetHeight(20);
       paint.Clear(UNCOLORED);
      paint.DrawStringAt(0, 0, s3, &Font16, COLORED);//
      epd.SetFrameMemory(paint.GetImage(), 180, 153, paint.GetWidth(), paint.GetHeight());
       //-------------paint是速度以及距离等数据区域
      paint.SetWidth(32);
      paint.SetHeight(70);
      //第二步:显示运行速度
      paint.Clear(UNCOLORED);
      paint.DrawStringAt(0, 0, s1, &Font24, COLORED);//这是字符在局部刷新块里面的位置
      epd.SetFrameMemory(paint.GetImage(), 50, 110, paint.GetWidth(), paint.GetHeight());//这是局部刷新块的座标
      //第三步:显示距离
      paint.Clear(UNCOLORED);
      paint.DrawStringAt(0, 0, s2, &Font24, COLORED);
      epd.SetFrameMemory(paint.GetImage(), 50, 15, paint.GetWidth(), paint.GetHeight());  
      //第四步:显示心率图形
      paint.SetWidth(98);
      paint.SetHeight(136);
      paint.Clear(UNCOLORED);
      Serial.print("hrm:");
      Serial.println(hrms);   //串口打印一个心率值
      //在paint区域显示心率图形
        for (int i = 0; i < maxRow; i++) {
          paint.DrawVerticalLine(i, int(maxLine - val*multiplicator),  3, COLORED);//用垂直线表达心率值,并且按照比例尺进行调整
        }
        hrms       = 0;
        epd.SetFrameMemory(paint.GetImage(), 93, 9, paint.GetWidth(), paint.GetHeight());//这是局部刷新块的座标
        epd.DisplayFrame();
        delay(500);
        smartDelay(2500);
      }
    }

    static void smartDelay(unsigned long ms)                // This custom version of delay() ensures that the gps object is being "fed".
    {
      unsigned long start = millis();
      do
      {
        while (Serial3.available())   //serial2可用
          gps.encode(Serial3.read());  //读取serial2数据
      } while (millis() - start < ms);
    }
    [/mw_shl_code]

    【图片一组】
    pcb.PNG


    【小结】
    一直对穿戴设备感兴趣,这次利用 esp32 +电子墨水屏,在这个方向上试着再向前走一步。表的体积还是有点大,而且没有做电源优化,也没有进行其他功能提升,仅仅就是一块能够显示心率(以及图形),运动速度,距离以及搜星等基本数据。方便,实用。这是我做的为数不多的实用器。也期待继续改进,不断升级。
    【后续提升】
    G ps模块提升的空间还是蛮大的。后续我会尝试将gps模块(含透透传)与我的一个仿谷歌眼镜结合起来。届时也会和大家分享。

    【鸣谢】
    感谢 arduino.cn以及第六届比赛提供的平台个人文库以及海神师兄的亚克力激光切割以及3D打印服务,我将分享相关的STL文件。
    感谢孝肃师兄的鼓励。
    祝福第六届开源比赛圆满成功,也期待更多玩家好玩的作品。
    沧海抱拳。

    分享:所有的外壳stl均分享。
    ch_UPLOAD0423.rar (159.72 KB, 下载次数: 13)
    表盘2.jpg
    手表盘2.png
  • TA的每日心情

    2020-6-8 07:16
  • 签到天数: 243 天

    [LV.8]以坛为家I

    发表于 2019-4-16 00:15 | 显示全部楼层
    不错,支持~

  • TA的每日心情
    开心
    2020-7-1 09:10
  • 签到天数: 817 天

    [LV.10]以坛为家III

    发表于 2019-4-16 08:58 | 显示全部楼层
    没有实物效果图吗
  • TA的每日心情
    开心
    2019-7-17 13:13
  • 签到天数: 264 天

    [LV.8]以坛为家I

     楼主| 发表于 2019-4-17 22:18 | 显示全部楼层

    pcb正在打板路上,3d建模也正在进行中,会不断更新。

    该用户从未签到

    发表于 2019-4-23 21:32 来自手机 | 显示全部楼层
    不错  楼主搞得挺好的!!

    该用户从未签到

    发表于 2019-10-10 15:16 | 显示全部楼层
    RE: BLE胸带+GPS墨水屏运动心率表 [修改]
  • TA的每日心情
    奋斗
    2019-12-25 15:11
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2019-12-24 09:30 | 显示全部楼层
    做的挺好的  ,赞一个

    该用户从未签到

    发表于 2020-4-16 14:57 | 显示全部楼层
    GPS模块功耗3.3V 7mA
    HTGNSS LCC GPS模块选型表.png
    HTGNSS天线模块一体GPS选型表.png
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    热门推荐

    5分钟带你快速了解新一代开发板:M5STACK
    5分钟带你快速了解新一代
    一、什么是M5Stack M5Stack是一种模块化、可堆叠扩展的开发板,每个模块
    创客火首发无人机编队套装,开启不一样的人工智能教育
    创客火首发无人机编队套装
    2017年国务院发布《新一代人工智能发展规划》,提出要广泛开展人工智能科普活动,在中
    为开发板 Generic ESP8266 Module 编译时出错
    为开发板 Generic ESP8266
    第一次尝试Arduino UNO软串口通信,编译一直通不过,错误信息 “开发板 generic (平
    请问L298N能不能驱动这种电机呀
    请问L298N能不能驱动这种
    小白不太敢确定orzz电机是30W,10转,12V的直流减速电机 谢谢大家了!
    ws2812灯带求助,求大佬。。
    ws2812灯带求助,求大佬。
    刚才那个求助帖我说的可能有点模糊,所以我这次弄详细一点。小弟第一次接触这个灯带,
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
    快速回复 返回顶部 返回列表