【教程】使用分离式蓝牙gps做一块Twatch运动手表-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 7159|回复: 2

【教程】使用分离式蓝牙gps做一块Twatch运动手表

[复制链接]
发表于 2021-1-10 20:28 | 显示全部楼层 |阅读模式
本帖最后由 沧海笑1122 于 2021-1-10 20:43 编辑

           使用分离式蓝牙gps做一块Twatch运动手表


【前言】

    我喜欢跑步,也喜欢极客电子。两个爱好的结合贯穿了这几年的很多玩具。从2017年开始陆续尝试了几个版本的运动手表制作。从一开始基于microduino 644p的硕大手表,到2019年基于esp32的蓝牙运动心率表。尝试了从光电式心率传感器到gps+蓝牙心率带等几种形式。
    这次项目有所不同,我设计了一个分离式的经典蓝牙+gps的模块,手表部分则是直接利用TWTACH平台,使用lvgl设计了一个简单漂亮的表盘。这样就构成了一个简单实用,运行良好的蓝牙gps运动手表。首先让我们看一下成品的样子吧。


户外_20210110182211_副本.jpg

   帽子2.jpg 帽子1.jpg

一、        项目基本情况

    早在2017年,我就基于m5stack+gps扩展板,实现了一个运动手表。虽然表盘硕大(50*50mm),戴出去还是很吸引眼球的。除了体积以外,最大的遗憾就是GPS搜星效果不尽如人意。受安装位置所限,当时使用的GPS天线是窄条的矩形天线,反射面积小、搜星效果不理想。这就形成了两难,如果要手表体积小巧,那么将有可能受制于GPS天线的位置和尺寸,从而牺牲搜星用户体验。因此从2019年开始,我尝试了将GPS模块与手表分离的方式。并且在2019年arduino.cn组织的极客竞赛中,提交了一款分离式的蓝牙心率手表。在那款手表中,使用了一对HC-08模块将gps数据传送到esp32侧,从而展示在墨水屏表盘上。由于是分离式GPS,因此搜星效果大大提升。这一版的遗憾仍然是GPS模块和手表的体积都太大。

    我从2020年底开始设计新的一版运动手表。这次没有用一对蓝牙模块,而是在GPS侧使用了HC-05模块+GPS的方式,在手表侧选择了Twatch作为平台。利用esp32的经典蓝牙功能,与GPS侧的HC-05建立了数据联络。从而在兼顾搜星效果的前提下,最大限度缩小了手表体积。以下是项目示意图。



二、        硬件准备


名称 型号         备注
1 GPS模块 BN-220
2 蓝牙模块 HC-05 蓝牙2.1 主机模式
3 电源管理模块+microusb公头 含充放电管理
4 GPS模块外壳 3D打印
5 锂电池 200毫安时
6 手表        TWATCH2020

蓝牙gps零件.jpg


三、        软件准备


名称         版本         备注
1 Lvgl库for arduino 7.3.1 制作漂亮的UI
2 Arduino Ide 1.8.13
3 BluetoothSerial for esp32 1.0
4 CodeBlocks 20.03 C++ ide,模拟设计lvgl表盘
5 Tinygps++ 1.02 最好的arduino下的gps解析库
6 Autodesk Inventor         V2020 外壳建模

四、        手表端代码部分


  1. <div style="text-align: left;">//=============================</div><div style="text-align: left;">//date:2020-11-16</div><div style="text-align: left;">//基于lvgl的twatch gps手表</div><div style="text-align: left;">//基本原理:gps传感器+HC-05蓝牙模块,构成了蓝牙传感器,twacth(esp32)使用经典蓝牙与之连接</div><div style="text-align: left;">//          将搜星的卫星数量、运动速度、当前gps时间以及经纬度提取解析,用以分析</div><div style="text-align: left;">//          搜星数量、实时运动速度、累计本次运动时间(分钟)以及本次运动累计里程(公里)</div><div style="text-align: left;">//          使用lvgl进行展示。</div><div style="text-align: left;">//作者:沧海</div><div style="text-align: left;">//参考:1、BluetoothSerial 官方库例题</div><div style="text-align: left;">//      2、TinyGPS++官方库例题</div><div style="text-align: left;">//      3、twatch 官方例题,感谢lewis师兄指导</div><div style="text-align: left;">//=============================</div><div style="text-align: left;">
  2. </div><div style="text-align: left;">
  3. </div><div style="text-align: left;">//======库声明及实例</div><div style="text-align: left;">// GPS解析库部分</div><div style="text-align: left;">#include <TinyGPS++.h></div><div style="text-align: left;">//Twatch手表 部分</div><div style="text-align: left;">#include "config.h"</div><div style="text-align: left;">TTGOClass *ttgo;</div><div style="text-align: left;">//蓝牙串口部分</div><div style="text-align: left;">#include "BluetoothSerial.h"</div><div style="text-align: left;">char *pin = "1234"; //建立连接时的密码,否则与hc05连接不上</div><div style="text-align: left;">BluetoothSerial SerialBT;</div><div style="text-align: left;">// 建立 TinyGPS++ object</div><div style="text-align: left;">TinyGPSPlus gps;</div><div style="text-align: left;">
  4. </div><div style="text-align: left;">//power&irq </div><div style="text-align: left;">AXP20X_Class *power;</div><div style="text-align: left;">bool irq = false;</div><div style="text-align: left;">
  5. </div><div style="text-align: left;">//=====全局变量声明</div><div style="text-align: left;">//定义gps测距变量</div><div style="text-align: left;">//上一次经纬度、本次经纬度、速度、可用卫星数、时间</div><div style="text-align: left;">float lst_lat, lst_lng, f_lat, f_lng, f_speed, f_sate,f_min,lst_min; </div><div style="text-align: left;">double f_dist; //本次测距的距离</div><div style="text-align: left;">String s_sat; //卫星数量</div><div style="text-align: left;">float my_min; //运动时间</div><div style="text-align: left;">
  6. </div><div style="text-align: left;">//============控件的全局定义</div><div style="text-align: left;">static lv_obj_t *label5 = NULL;// label控件,显示搜星数量</div><div style="text-align: left;">static lv_obj_t *gauge1 = NULL; //仪表控件以及label子控件,显示实时速度</div><div style="text-align: left;">static lv_obj_t *label3 = NULL;</div><div style="text-align: left;">static lv_obj_t * arc2= NULL;//圆弧控件及label子类,显示累计里程</div><div style="text-align: left;">static lv_obj_t * label2= NULL;</div><div style="text-align: left;">static lv_obj_t * bar1 = NULL;//bar控件及label子类,显示累计运动时间(分钟)</div><div style="text-align: left;">static lv_obj_t * label4 = NULL;</div><div style="text-align: left;">
  7. </div><div style="text-align: left;">//========= lvgl font声明</div><div style="text-align: left;">LV_FONT_DECLARE(digital_play_st_24);</div><div style="text-align: left;">LV_FONT_DECLARE(robot_light_16);</div><div style="text-align: left;">LV_FONT_DECLARE(quostige_16);</div><div style="text-align: left;">LV_FONT_DECLARE(exninja_22);</div><div style="text-align: left;">
  8. </div><div style="text-align: left;">static void event_handler(lv_obj_t *obj, lv_event_t event)  //实体按钮关断电源</div><div style="text-align: left;">{</div><div style="text-align: left;">if (event == LV_EVENT_CLICKED) {</div><div style="text-align: left;">Serial.printf("Power Off\n");</div><div style="text-align: left;">delay(1000);</div><div style="text-align: left;">ttgo->shutdown();  //立即关断电源</div><div style="text-align: left;">} else if (event == LV_EVENT_VALUE_CHANGED) {</div><div style="text-align: left;">}</div><div style="text-align: left;">}</div><div style="text-align: left;">
  9. </div><div style="text-align: left;">
  10. </div><div style="text-align: left;">void setup()</div><div style="text-align: left;">{</div><div style="text-align: left;">//手表初始化</div><div style="text-align: left;">ttgo = TTGOClass::getWatch();</div><div style="text-align: left;">ttgo->begin();</div><div style="text-align: left;">ttgo->openBL();</div><div style="text-align: left;">ttgo->lvgl_begin();</div><div style="text-align: left;">power = ttgo->power;</div><div style="text-align: left;">//串口以及蓝牙串口初始化</div><div style="text-align: left;">Serial.begin(9600);</div><div style="text-align: left;">SerialBT.setPin(pin);//在新的ble库里,需要此句有效</div><div style="text-align: left;">SerialBT.begin("twatch"); //Bluetooth device name</div><div style="text-align: left;">
  11. </div><div style="text-align: left;">//attachInterrupt button</div><div style="text-align: left;">pinMode(AXP202_INT, INPUT_PULLUP);</div><div style="text-align: left;">attachInterrupt(AXP202_INT, [] {</div><div style="text-align: left;">irq = true;</div><div style="text-align: left;">}, FALLING);</div><div style="text-align: left;">// Must be enabled first, and then clear the interrupt status,</div><div style="text-align: left;">// otherwise abnormal</div><div style="text-align: left;">power->enableIRQ(AXP202_PEK_SHORTPRESS_IRQ,</div><div style="text-align: left;">true);</div><div style="text-align: left;">
  12. </div><div style="text-align: left;">//  Clear interrupt status</div><div style="text-align: left;">power->clearIRQ();</div><div style="text-align: left;">
  13. </div><div style="text-align: left;">//数字以及颜色字体等初始化</div><div style="text-align: left;">static lv_style_t num_style;</div><div style="text-align: left;">lv_style_init(&num_style);</div><div style="text-align: left;">lv_style_set_text_color(&num_style, LV_STATE_DEFAULT, LV_COLOR_BLUE);</div><div style="text-align: left;">lv_style_set_text_font(&num_style, LV_STATE_DEFAULT, &exninja_22);</div>    <div style="text-align: left;">
  14. </div><div style="text-align: left;">
  15. </div><div style="text-align: left;">/*建立一个label用于显示搜星数量 */</div><div style="text-align: left;">label5 = lv_label_create(lv_scr_act(), NULL); //在画布,建立这个label5,用于显示搜星数量</div><div style="text-align: left;">lv_obj_align(label5,NULL, LV_ALIGN_CENTER, -30, -90); //设置label5的位置,画布左上角</div><div style="text-align: left;">
  16. </div><div style="text-align: left;">
  17. </div><div style="text-align: left;">/*  创建速度仪表gauge(跑速表)*/</div><div style="text-align: left;">static lv_color_t needle_colors[1]; //指针数组</div><div style="text-align: left;">needle_colors[0] = LV_COLOR_BLUE;  //指针为蓝色</div><div style="text-align: left;">gauge1 = lv_gauge_create(lv_scr_act(), NULL); //在画布上,新建一个跑速表,命名为gauge1</div><div style="text-align: left;">
  18. </div><div style="text-align: left;">lv_gauge_set_needle_count(gauge1, 1, needle_colors);//设置其指针颜色,此例中为蓝色</div><div style="text-align: left;">lv_obj_set_size(gauge1, 110, 120); //设置跑速表在画布上的尺寸</div><div style="text-align: left;">lv_gauge_set_range(gauge1,0,10); //设置跑速表的测量范围,默认是0~100,此处用于跑步速度,所以根据本人的实际情况,设置为0~10</div><div style="text-align: left;">lv_gauge_set_critical_value(gauge1,9); //设置跑速表警示区域,从8开始</div><div style="text-align: left;">lv_obj_align(gauge1, NULL, LV_ALIGN_CENTER, -60, 0); //设置跑速表在画布中的位置</div><div style="text-align: left;">
  19. </div><div style="text-align: left;">//====设置一个仪表的子类label3</div><div style="text-align: left;">label3 = lv_obj_get_child(gauge1, NULL); //在跑速表中设置一个子类,label3</div><div style="text-align: left;">label3 = lv_label_create(gauge1, label3); //在跑速表的显示区域,建立这个label3</div><div style="text-align: left;">lv_obj_add_style(label3, LV_OBJ_PART_MAIN, &num_style);</div><div style="text-align: left;">lv_obj_align(label3, gauge1, LV_ALIGN_CENTER, 0, 40); //设置label3在跑速表中的位置</div><div style="text-align: left;">
  20. </div><div style="text-align: left;">//-----设置arc2 显示里程</div><div style="text-align: left;">arc2 = lv_arc_create(lv_scr_act(), NULL); //在画布上建立一个arc对象,名字是arc2</div><div style="text-align: left;">lv_obj_set_size(arc2,  110, 120);  //设置arc的显示尺寸</div>    <div style="text-align: left;">
  21. </div><div style="text-align: left;">//label2子类</div><div style="text-align: left;">label2 = lv_obj_get_child(arc2, NULL); //在arc2中设置一个子类,名字是label2,这个子类是显示在arc里面的</div><div style="text-align: left;">label2 = lv_label_create(arc2, label2); //在arc2的显示区域内,设立这个label2</div><div style="text-align: left;">lv_obj_align(label2, arc2, LV_ALIGN_CENTER, 0, 0); //设置label2在arc2里面的对齐方式。</div><div style="text-align: left;">lv_obj_align(arc2, NULL, LV_ALIGN_CENTER, 55, 0); //设置arc2在画布上的对齐方式。</div><div style="text-align: left;">
  22. </div><div style="text-align: left;">//显示累计运动时间</div> <div style="text-align: left;">
  23. </div><div style="text-align: left;">bar1 = lv_bar_create(lv_scr_act(), NULL);</div><div style="text-align: left;">lv_obj_set_size(bar1, 170, 20);</div><div style="text-align: left;">lv_obj_align(bar1, NULL, LV_ALIGN_CENTER, 0, 90);</div><div style="text-align: left;">//// /*Set the lables*/////</div><div style="text-align: left;">label4 = lv_obj_get_child(bar1, NULL); //在bar中设置一个子类,label4</div><div style="text-align: left;">label4 = lv_label_create(bar1, label4); //在bar的显示区域,建立这个label4</div><div style="text-align: left;">lv_obj_align(label4, bar1, LV_ALIGN_CENTER, 65,0); //设置label4在bar中的位置</div><div style="text-align: left;">
  24. </div>    <div style="text-align: left;">
  25. </div><div style="text-align: left;">//建立一个任务函数,每隔3000ms,执行一次任务</div><div style="text-align: left;">lv_task_create(printsats_task, 3000, LV_TASK_PRIO_MID,nullptr);</div><div style="text-align: left;">
  26. </div><div style="text-align: left;">// Set 20MHz operating speed to reduce power consumption</div><div style="text-align: left;">setCpuFrequencyMhz(20);</div><div style="text-align: left;">}</div><div style="text-align: left;">
  27. </div><div style="text-align: left;">
  28. </div><div style="text-align: left;">void loop()</div><div style="text-align: left;">{</div><div style="text-align: left;">lv_task_handler();  //调用lvgl定期任务</div><div style="text-align: left;">delay(5);</div><div style="text-align: left;">if (millis() > 5000 && gps.charsProcessed() < 10)</div><div style="text-align: left;">Serial.println(F("No GPS data received: check wiring"));</div><div style="text-align: left;">
  29. </div><div style="text-align: left;">// Wait for the power button to be pressed</div><div style="text-align: left;">while (irq) {</div><div style="text-align: left;">delay(1000);</div><div style="text-align: left;">power->clearIRQ();</div><div style="text-align: left;">ttgo->shutdown();  //立即关断电源</div><div style="text-align: left;">
  30. </div><div style="text-align: left;">}</div><div style="text-align: left;">
  31. </div>    <div style="text-align: left;">
  32. </div><div style="text-align: left;">}</div><div style="text-align: left;">
  33. </div><div style="text-align: left;">
  34. </div><div style="text-align: left;">// This custom version of delay() ensures that the gps object</div><div style="text-align: left;">// is being "fed".</div><div style="text-align: left;">static void smartDelay(unsigned long ms)</div><div style="text-align: left;">{</div><div style="text-align: left;">unsigned long start = millis();</div><div style="text-align: left;">do </div><div style="text-align: left;">{</div><div style="text-align: left;">while (SerialBT.available())</div><div style="text-align: left;">gps.encode(SerialBT.read());</div><div style="text-align: left;">} while (millis() - start < ms);</div><div style="text-align: left;">}</div><div style="text-align: left;">
  35. </div><div style="text-align: left;">void printsats_task(lv_task_t * task)  //定期执行的任务</div><div style="text-align: left;">{</div><div style="text-align: left;">//========显示卫星数量</div><div style="text-align: left;">int val_sats=gps.satellites.value(); //卫星数量解析后赋值给val_sats</div><div style="text-align: left;">Serial.println(val_sats);</div><div style="text-align: left;">//int val_sats =random(3, 12) ;//调试中使用一个3~12之间的随机数,模拟卫星数量,正式运行注释掉</div><div style="text-align: left;">String s_sat=String(val_sats);  //将Int转换为string</div><div style="text-align: left;">String temp = String(LV_SYMBOL_GPS) +" "+ s_sat;  //将GPS符号与搜星数量合并为一个string</div><div style="text-align: left;">const char* c_sat = temp.c_str();//c_sat即为合并后并且转换的结果  const char c_sat</div><div style="text-align: left;">lv_label_set_text(label5,c_sat);//给label5赋值,这个数值为搜星的卫星数量,动态数据,来自gps</div><div style="text-align: left;">//同时旁边带一个gps的符号</div><div style="text-align: left;">
  36. </div><div style="text-align: left;">//==========显示实时速度</div><div style="text-align: left;">float f_speed= gps.speed.kmph(); //实际运行时改为此句</div><div style="text-align: left;">//float f_speed=float(random(10, 100)/10) ;//使用一个0~10之间的随机数,模拟跑步速度</div> <div style="text-align: left;">
  37. </div><div style="text-align: left;">lv_gauge_set_value(gauge1, 0,f_speed);  //赋值,这个数值在运行中,从gps读取即时速度</div><div style="text-align: left;">String s_speed=String(f_speed);  //将传递过来的Int转换为string</div><div style="text-align: left;">const char* c_speed = s_speed.c_str();//c_speed转换的结果  const char c_speed</div><div style="text-align: left;">lv_label_set_text(label3 ,c_speed); //给label3赋值,这个数值与跑速表上的指针位置是一个数值,来自gps的speed</div><div style="text-align: left;">
  38. </div>    <div style="text-align: left;">
  39. </div><div style="text-align: left;">//===========显示累计里程</div><div style="text-align: left;">//=====计算里程</div><div style="text-align: left;">f_lat=gps.location.lat();</div><div style="text-align: left;">f_lng=gps.location.lng();</div><div style="text-align: left;">if (lst_lat == 0) {</div><div style="text-align: left;">f_dist = f_dist;</div><div style="text-align: left;">}</div><div style="text-align: left;">else</div><div style="text-align: left;">{</div><div style="text-align: left;">f_dist = f_dist + gps.distanceBetween(f_lat, f_lng, lst_lat, lst_lng) / 1000;</div><div style="text-align: left;">Serial.println(f_dist);</div><div style="text-align: left;">}</div><div style="text-align: left;">lst_lat = f_lat;//计算后,将现坐标赋值为前坐标,下同</div><div style="text-align: left;">lst_lng = f_lng;</div>   <div style="text-align: left;">
  40. </div>    <div style="text-align: left;">
  41. </div><div style="text-align: left;">//float f_dist=float(random(10, 100)/10) ;//使用一个0~10之间的随机数,模拟跑步速度,实际使用时此句删除</div><div style="text-align: left;">lv_arc_set_angles(arc2, 0, 36*f_dist);  //设置arc2的圆弧的角度值,这个值在运行中,将从里程里面取数,然后折算成角度</div><div style="text-align: left;">//按照累计10公里,360度计算,每公里约36度。</div><div style="text-align: left;">String s_dist=String(f_dist);  //将传递过来的Int转换为string</div><div style="text-align: left;">const char* c_dist = s_dist.c_str();//c_speed转换的结果  const char c_dist</div><div style="text-align: left;">lv_label_set_text(label2 ,c_dist); //给label3赋值,这个数值与跑速表上的指针位置是一个数值,来自gps距离累加</div><div style="text-align: left;">// lv_label_set_text(label2 ,"3.5"); //赋值,这个值在运行中是更新的,从里程里面取数,与圆弧的角度是关联的。 </div>    <div style="text-align: left;">
  42. </div><div style="text-align: left;">//===========显示累计运动时间</div><div style="text-align: left;">f_min=gps.time.hour()*60+gps.time.minute()+gps.time.second()/60;</div><div style="text-align: left;">if (lst_min == 0) {</div><div style="text-align: left;">my_min = my_min;</div><div style="text-align: left;">}</div><div style="text-align: left;">else</div><div style="text-align: left;">{</div><div style="text-align: left;">my_min = my_min + f_min-lst_min;</div><div style="text-align: left;">}</div><div style="text-align: left;">lst_min = f_min;//计算后,将现分钟数存为原分钟数</div>   <div style="text-align: left;">
  43. </div>    <div style="text-align: left;">
  44. </div><div style="text-align: left;">//float f_min=float(random(10, 600)/10) ;//使用一个0~60之间的随机数,模拟跑步速度</div><div style="text-align: left;">lv_bar_set_value(bar1, my_min*100/60, LV_ANIM_OFF);//设置bar的值,这个值在运行中,将从gps时间里面取数,然后折算value</div><div style="text-align: left;">//按照累计60分钟,100计算,每分钟约100/60。</div><div style="text-align: left;">String s_min=String(my_min);  //将传递过来的Int转换为string</div><div style="text-align: left;">const char* c_min = s_min.c_str();//c_min转换的结果  const char c_min</div><div style="text-align: left;">lv_label_set_text(label4 ,c_min); //给label4赋值,这个数值与bar上的value位置是一个数值,来自gps的时间累计</div><div style="text-align: left;">
  45. </div>    <div style="text-align: left;">
  46. </div><div style="text-align: left;">smartDelay(0);</div><div style="text-align: left;">}</div>
复制代码
setup_2021-01-10_20-02-13.png

五、        制作过程

(一)        蓝牙GPS模块的制作

蓝牙gps部分由三个配件组成:一是gps模块,我选用了一款比较小巧的gps模块,陶瓷天线一体;二是蓝牙模块,使用汇承HC-05主从一体模块,带底板,需要将其设置为主机模式。三是电源管理模块以及microusb公头,带一个精巧的按键,通过单击唤醒、双击关断电源,非常友好。输出为5V。电池选用200毫安时锂聚合物电池。虽然体积和容量都不大,但是待机时间实测2小时左右,可以满足一次跑步的能量。

示意图_2021-01-10_20-01-48.png


将各个部分连接好,并且进行测试后,方可进入焊接环节。因为连线比较少,使用飞线将其焊接即可。在焊接结束后,用热熔胶将各个部分固定。这样做的目的,一是在跑步过程中,各个部件结合比较紧密,不易松动。二是便于测绘,确定尺寸后,用于外壳的3D建模。

蓝牙gps组装件1.jpg 重量.jpg

(二)        手表部分:

Twatch2020是LILYGO出品的一个很不错的智能穿戴的极客平台,主控是esp32(Flash16MB,Psram 8MB);显示屏是一块1.54寸的LCD触摸屏(屏幕分辨率:240*240)。通过触摸屏以及一个实体按钮进行人机交互。表盘的长宽分别是34.73mm和42.36mm,整个尺寸就是一块正常手表的尺寸。因为我们利用了esp32的蓝牙功能(非ble,而是经典蓝牙)所以在手表端就不用考虑外接蓝牙模块。手表端主要的设计功能是表盘以及gps数据的解析与展示。

TTGO_2021-01-10_20-20-06.png

1、        关于表盘设计
表盘主要有四个主要信息
(1)        搜星数量:用以观察GPS模块是否正常工作。
(2)        累计运动时间:观察本次运动的时间,单位是分钟,使用lvgl的bar组件,用一条进度棒表达累计运动时间。
(3)        累计运动里程:单位是公里。使用lvgl的arc组件,用一段圆弧显示运动里程。
(4)        实时运动速度。使用lvgl的码表组件,看上去像一个车辆的速度表。
表盘设计过程中,使用CodeBlocks进行模拟设计,这样可以减少对arduino ide的编译、下载等迭代过程,大大提升设计效率。

码表_2021-01-10_20-20-23.png

2、        运动手表主要功能
在接收到gps发来的数据后,经过tinygps++库的解析,形成了搜星数量、累计运动时间、累计运动里程(公里)以及运动时速等信息。受表盘限制,将跑步运动比较常用的累计运动时间、里程以及时速放在显示区比较合理的位置,比如运动时速一般跑步我的速率在6~8公里,就将这个速度放在比较合适的显示位置、便于观察。
程中建立了lvgl定期调度的任务,每隔一段时间(我设计了3秒)就运行一次任务,完成gps数据解析至表盘展示的工作。

(三)        蓝牙GPS模块的调试
第一步:HC-05模块+usbtll与PC连接
使用一个usbtll与HC-05连接,然后使用PC的串口调试工具,观察HC-05工作是否正常。
第二步:对HC-05的设置,设置为主机模式,手表端的esp32将作为从机与其相连。
我使用的HC-05上由一个key按键,可以切换AT设置模式或者蓝牙运行模式。注意:AT方式下的波特率是38400bps。
按照以下顺序设置HC-05:
1 AT+ORGL # 恢复出厂模式
2 AT+NAME = hc05 # 设置蓝牙名称
3 AT+ROLE=1 # 设置蓝牙为主模式
4 AT+PSWD=1234 # 与ESP32密码一致
5 AT+INIT #初始化SPP规范库便于查询周边信号
  这一步操作时,需要按动key键,设置后,注意通过led闪动观察模块是否还在AT模式,否则后续操作无法进行

4 AT + BIND = <ESP32地址> #绑定ESP32蓝牙地址
5 AT + PAIR = <ESP32地址> #与ESP32配对
6 AT + LINK = <ESP32地址> #与ESP32连接
7 AT+CMODE=0 # 设置蓝牙只与绑定地址连接

关于这一步操作,为了使这个蓝牙GPS模块通用,我就将此功能设置为:
AT+CMODE=1 # 设置蓝牙不与特定绑定地址连接,这样就可以方便地与M5STACK以及其他esp32设备进行交互。此处玩家可以根据自己的需要进行设置。
另外,关于HC-05波特率默认9600,玩家可以自行设置,因为我的GPS模块就是9600,所以此处我没有做调整。
第三步:加上gps之后,通过PC实现的联调
此步骤目的是验证HC-05与GPS的连接以及工作正常。将蓝牙模块HC-05与GPS模块、电源模块以及锂电池连接后,实现与PC端(通过另外一片HC-05从机模块)联调,从PC端接收到HC-05+GPS发送来的数据,这一步就算成功了。
第四步:使用gps真实数据与Twatch手表联调。
因为arduino ide的编译和下载速度比较慢。所以我一般是使用PC的串口工具,模拟定时发送(约间隔3秒)给twatch手表发送模拟gps数据,同时观察twatch端的gps数据解析是否正常,然后进行相应调整。在此步骤完成后,会对整个系统进行联调。
    综上所述,这个小玩具比较简单。主要知识点就是蓝牙模块的设置、与GPS的连接以及GPS数据的解析。我采用了分步调试,在分步调试都正常的情况下,进行整体联调。成功率比较高,事半而功倍。

(四)        蓝牙GPS模块外壳的设计
使用Autodesk的inventor  2020进行建模以及装配模拟。外壳分两部分顶板以及底壳。可以装配在帽子上,这样,GPS天线就可以保持最大限度朝向天空。在底壳设计时,考虑了两个5mm的内柱,预埋了直径4mm的铜镶嵌件作为螺母,用直径3mm的螺丝将模块固定至帽子上。需要在帽舌正面开两个3mm的孔。

    顶壳_2021-01-10_20-01-26.png 底壳_2021-01-10_20-01-09.png 装配_2021-01-10_20-03-50.png


六、        路测以及使用情况
  •     跑步路测情况:在户外打开蓝牙gps模块电源,尽量在开阔地,将模块天线部分朝向天空,打开手表电源。看到搜星数量发生变化(非0)后,系统即可正常工作,根据天气、周边障碍物的情况,搜星时间从几分钟到十几分钟不等。总体还是可以接受。在跑步开始后,可以观察到手表端的四组数据都开始发生变化。
  •     车辆路测情况:虽然这是一块运动手表,我还是带到汽车上进行了一次车辆驾驶的测试。结果也正常,只是距离和速度等数据在表盘上都会溢出。但是数字部分还是正常显示。


【后记】

    至此,一块2020版的蓝牙运动手表就完成了。蓝牙模块部分整体重量28克(连同外壳),还有优化空间,如果自己绘制PCB,那么各个部件的集成度可以大幅缩减,留待后续进行提升吧。Twatch还有一款手表,带有TF卡槽,我在2017年曾经在m5stack的basic平台上写过一个gps轨迹存储的功能,下一步也可以移植到这里。这样可以跑步结束后,离线观察运动轨迹、运动中的速度变化等。如果加上ble心率,还可以观察心率变化过程,相当于一份简单的运动日志。

    关于智能穿戴的体积和待机,一直都是非常两难的话题。从上一版开始,我就想尝试将传感器(如gps)与穿戴分离,降低穿戴的重量。丰富传感器接入的范畴,比如小米和intel合作的一款跑鞋,里面也带有一个ble采集传感器,可以将步幅、速度、姿态等数据上送至专用APP,只可惜是专用的ble协议,否则这样的传感器也有希望接入到esp32中。那么手表就成了一个汇集各个传感器的hub以及处理展示中心。在不改变其体积的情况下,尝试更加丰富的玩法。


    在制作过程中,手表以及lvgl部分得到了lewis师兄的指点,也参考了来自因特网的一些帖子,比如:经典蓝牙与esp32的连接,给了我很多启发,在此一并致谢。


    感谢arduino.cn给我们提供这样交流的平台。
    在大寒和小寒之间,整理了这个帖子。
    寒冬凛冽,春天即在眼前。
    沧海抱拳。



附件分享:代码以及所需字体

Lvgl_Base_gps_1120.zip (42.37 KB, 下载次数: 18)

功能示意图_2021-01-10_20-02-48.png
帽子3.jpg
发表于 2021-1-13 09:32 | 显示全部楼层
厉害哦 !还会画外壳
 楼主| 发表于 2021-1-14 22:25 | 显示全部楼层
更新一下代码。亲测无误。
20210114.zip (4.27 KB, 下载次数: 21)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-12-1 01:11 , Processed in 0.112291 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表