【2024 DigiKey 创意大赛】自行车智能骑行助手
本帖最后由 Newhor 于 2024-11-27 09:38 编辑<p><strong><span style="font-size:24px;">自行车智能骑行助手</span></strong></p>
<p><span style="font-size:16px;">作者:newhor</span></p>
<p> </p>
<p><span style="font-size:20px;"><b>一、作品简介</b></span></p>
<p> </p>
<p><span style="font-size:18px;"><b>简介</b></span></p>
<p> 自行车智能骑行助手的想法是填补骑行配件功能的空缺,为户外骑行爱好者提供一个集胎压测量、体重监测分析及环境感知预警于一体的综合智能解决方案</p>
<p>市面上现有的骑行配件中,很少有监测骑行者长途骑行下的体重变化,胎压预警以及环境感知预警等功能。本作品主要特点如下:</p>
<p>①在骑行前,安装在自行车轮胎中的压力传感器采集的压力数据,可以监测轮胎的初始压力值,并通过ESP-NOW无线通信将数据传送到主控MCU,再通过前期的数据标定确定的函数关系,MCU将压力数据转换为胎压后,显示在屏幕上。当检测到胎压低于预设的安全范围(例如25PSI)时,智能助手会立即通过显示器提示骑行者进行胎压补充,确保骑行安全。具体实现包括:压力传感器安装在轮胎钢圈与内胎之间,测量初始压力值;ESP-NOW模块负责将传感器数据无线传输到主控MCU;主控MCU处理接收到的数据,并通过显示器显示给骑行者。</p>
<p> </p>
<p>②在长时间骑行时,系统通过采集安装在轮胎内的压力传感器的压力值,通过前期的标定,将其转化为体重值。结合骑行时间和BME680传感器提供的环境温度数据,系统通过算法计算一段时间内体重的变化量,如果体重变化量超过预设阈值,会触发警报,通过显示屏提醒用户,同时提供休息和补水建议。</p>
<p> </p>
<p>③通过拓展的BME680环境传感器,给骑行者提供户外骑行时的空气质量预警和高温预警。一旦发现空气质量下降或温度过高,智能助手将通过显示屏及时提醒骑行者采取相应措施,如减少运动强度或寻找遮阴处休息,确保骑行者的健康和安全。</p>
<p> </p>
<p>④通过车把端的MCU采集并记录BME680传感器在一段时间内的气压和湿度数据,MCU对这些数据进行分析,当检测到气压持续降低且空气湿度升高超过一定阈值时,提供下雨预警,通过显示器告警骑行者,建议提前返回或寻找避雨场所,确保骑行安全。</p>
<p> </p>
<p>⑤可记录骑行过程中的体重、胎压、环境变化数据保存在本地(后续添加手机端无线接收功能),并提供长期趋势分析,帮助骑行者更好地了解自身状况和改善骑行计划。</p>
<p>此外,该还系统具有强大的可拓展性,以后可以集成更多好玩的功能进来。</p>
<p> </p>
<p><strong>主要特点:</strong></p>
<ul>
<li>胎压监测</li>
<li>实时监测体重变化</li>
<li>智能骑行建议</li>
<li>恶劣天气预警</li>
<li>可存储运动数据,分析长期数据</li>
<li>硬件模功能块化设计,功能易扩展</li>
<li>可太阳能充电</li>
<li>开源</li>
</ul>
<p style="list-style-type:none"> </p>
<p style="list-style-type:none"><span style="font-size:18px;"><b>照片</b></span></p>
<p style="list-style-type:none"><b>(1)车把数据接收显示端</b></p>
<p> </p>
<p> </p>
<p style="list-style-type:none"><b>(2)</b><b>轮胎数据采集发送端</b></p>
<p> <b> </b> <b> </b> </p>
<p> </p>
<p> </p>
<p><strong><span style="font-size:20px;">二、系统框图</span></strong></p>
<ol>
<li><span style="font-size:18px;"><b>硬件说明</b></span></li>
</ol>
<p> </p>
<p>主要物料</p>
<ul>
<li value="50">控制器:ESP32-C6,ESP32-S2MINI</li>
<li value="50">加速度传感器:4554、MMA8452</li>
<li value="50">湿度空气传感器:BME680</li>
<li value="50">显示屏幕:1.8寸TFT</li>
<li value="50">充电管理芯片:TP4054</li>
<li value="50">薄膜式压力传感器</li>
<li value="50">锂电池、太阳能板</li>
</ul>
<p> </p>
<p><span style="font-size:16px;"><b>硬件框图</b></span></p>
<p>主要分为车轮压力数据采集端、以及车把数据接收显示端,采用模块集成方式、方便更换与拓展。</p>
<p> </p>
<p> </p>
<p><strong><span style="font-size:18px;"> 2.软件说明</span></strong></p>
<p> </p>
<p> 软件分为4个部分:</p>
<ul>
<li value="50">MCU数据处理</li>
<li value="50">ESPNOW数据传输</li>
<li value="50">传感器数据采样控制</li>
<li value="50">液晶显示</li>
</ul>
<p> </p>
<p><strong><span style="font-size:20px;">三、各部分功能说明</span></strong></p>
<ol>
<li><span style="font-size:18px;"><b>硬件功能</b></span></li>
</ol>
<p>(1)轮胎端:主要完成胎压数据采集、ESPnow数据发送等功能</p>
<p> </p>
<ul>
<li value="50">轮胎端主控使用ESP32-S2MINI,这是一款乐鑫一款入门级开发板,搭载Xtensa®32位LX7单核处理器,工作频率高达240MHz。可深度休眠,利用低功耗协处理器监测外设的状态变化或某些模拟量多的阈值,可通过外部中断唤醒,常用于可穿戴电子设备、智能家居等场景常用于物联网设备开发。在该作品中,利用其ADC功能,采集安装于轮胎内壁的薄膜式压力传感器的电压值,并通过ESPnow无线功能,实时将数据上传到车把端的MCU进行处理。</li>
<li value="50">加速度传感器采用了基础型号MMA8452,加速度传感器的中断输出引脚,连接到轮胎端ESP32MINI上可支持休眠唤醒的IO口,并在采样程序中配置了休唤醒中断。当自行车静止一段时间后,ESP32MINI由于未检测到加速度传感器的运动中断,会进入深度休眠降低功耗,当检测到轮胎震动时,加速度传感器会立即产生运动中断唤醒MCU,开始采集压力数据,实现了轮胎端采样系统的低功耗。</li>
</ul>
<p align="justify"> </p>
<p align="center"> </p>
<ul>
<li value="50">压力传感器采用薄膜式,方便安装于轮胎与钢圈之间,当轮胎由于胎压或骑行者体重产生形变时,薄膜式压力传感器受到压力产生相应的形变,其电阻值产生相应变化,MCU通过电阻分压电路的方式进行ADC采样,将压力值转换为电压值。</li>
</ul>
<p> </p>
<ul>
<li value="50">供电部分,配备了电池,并基于TP4054芯片设计了可多路供电的防反充电电路,通过添加多个二极管防止反充,实现开发板的USB接口以及太阳能板双充电模式。</li>
</ul>
<p> </p>
<p> (2)车把端:主要完成数据接收,处理、以及数据显示等功能</p>
<p> </p>
<ul>
<li value="50">车把端主控使用大赛提供的ESP32-C6开发板,是一款高性能、低功耗的单芯片解决方案。多达 22 个 GPIO 引脚,支持 SPI、I2C、UART 等多种通信协议,具有 Wi-Fi 6 和 蓝牙低能耗(Bluetooth LE 5.2) 的支持,主要面向现代无线通信需求。该作品采用该模块作为主控接收端MCU,能满足ESPNOW无线功能高频的数据接收和处理需求,并同时实现了对多个传感器的数据采集处理,以及显示。</li>
<li value="50">显示器使用ST7735驱动TFT屏,支持全彩显示、内容方向可调整,数据显示量够大,且观感更好。</li>
<li value="50">加速度传感器模块使用的是大赛提供的4554,ICM-20948 是一款高性能的9轴运动传感器,具有高精度与低噪声等特点,在该作品中,该模块用于判断自行车实时的骑行状态,是加减速还是匀速,帮助MCU处理数据,消除加减速带来的轮胎形变时产生的误差数据。</li>
<li value="50">环境传感器使用的是大赛提供的BME680模块,具有环境参数丰富,精度较高等特点。在该作品中,主要用于采样环境参数,包括温湿度、空气质量、大气压等。其中温度数据,用于辅助长时间骑行时体重变化量供相应的休息和补水建议;通过分析湿度和大气压数据下雨前的变化趋势,提前预测降雨的可能性,并通过屏幕向骑行者发出提醒;空气质量数据,实时转换为空气质量IAQ,给户外骑者相应的户外骑行建议。</li>
<li value="50">供电电路与轮胎端一致,采用电池供电,并支持USB与太阳能板双充电。</li>
</ul>
<p><span style="font-size:18px;">2.<b>软件功能</b></span></p>
<p><b> (1)数据标定以及转换部分</b></p>
<p>在胎压数据标定时,由于传感器的精度以及受到安装位置的影响,会出现相同胎压下前后轮的压力传感器初始压力不一致的现象,故前后轮的压力数据分开进行标定。压力传感器安装于车轮内部后,通过带压力显示的打气筒给轮胎补充到多组不同的胎压值,得到对应胎压下的传感器压力数值,作为空载状态下,不同胎压与采集电压的数据组合,通过数据拟合得到相应的函数关系后,实现通过空载状态下电压计算出当前胎压数值的功能。</p>
<p>在体重数据标定时,通过记录多个不同体重骑行者,骑行时与空载的压力差值,与相应体重进行数据组合,然后进行数据拟合,得到电压差与骑行者体重函数关系。</p>
<p> <strong>(2)数据滤波部分</strong></p>
<p align="center"> </p>
<p> 骑行时压力传感器由于轮胎转动到不同的位置、路面颠簸、急加减速等因素,会产生部分误差数据。采用滑动平均法,对一段时间窗口内的数据进行平均,从而减少噪声或波动,使数据更加平滑。</p>
<p> </p>
<p><span style="font-size:20px;"><b>四、作品源码</b></span></p>
<p> </p>
<p><a href="https://download.eeworld.com.cn/detail/Newhor/635010">自行车智能骑行助手——作品源码-嵌入式开发相关资料下载-EEWORLD下载中心</a></p>
<p> 系统的硬件部分软件使用 micropython 进行开发,采用异步多任务形式。部分源码如下:</p>
<p> </p>
<p> 文件:task_TFTshow.py</p>
<pre>
<code class="language-python">import asyncio
from machine import Pin, SoftSPI
from time import sleep_ms
from global_var import *
from cfg import *
async def TFTshow():
while True:
if gv.int_flag == 1:
# pass
lcd.clear(0)
lcd.rotate(180)
lcd.drawText('系统初始化中'+str(gv.int_cnt*10)+'%',0,50,font,0x0f0)
lcd.drawText('请在10S内推动自',0,70,font,0xff0)
lcd.drawText('行车采集初始数据',0,90,font,0xff0)
lcd.show()
elif gv.str_flag == 1 or 2 or 3:
lcd.clear(0)
lcd.rotate(180)
lcd.drawText('温度:'+str(round(gv.tmp,1))+'°',0,0,font,0x0f0)
lcd.drawText('湿度:'+str(round(gv.hum,1))+'%',0,20,font,0xff0)
lcd.drawText('气压:'+str(round(gv.pre,1))+'Pa',0,40,font,0x00f)
lcd.drawText('IAQ:'+str(gv.IAQ)+gv.iaq_leve,0,60,font,0xff0)
lcd.show()
await asyncio.sleep(2)
ifgv.weight_int !=0:
lcd.clear(0)
lcd.rotate(180)
lcd.drawText('初始胎压psi:',0,0,font,0x00f)
lcd.drawText('前:'+str(gv.P1_int)+'后:'+str(gv.P2_int),0,20,font,0x00f)
lcd.drawText('初始体重:'+str(gv.weight_int)+'kg',0,40,font,0x0f0)
lcd.drawText('实时体重:'+str(gv.weight)+'kg',0,60,font,0x0f0)
# lcd.drawText('体重差值:'+str(gv.weight_int-gv.weight)+'kg',0,80,font,0x00f)
lcd.drawText('运动时长:'+str(round(gv.SportTime/60+0.7,1))+'min',0,100,font,0x00f)
lcd.show()
await asyncio.sleep(2)
gv.SportTime+=2
gv.SportTime+=2.1
if gv.end_flag ==1:#运动结束
lcd.clear(0)
lcd.rotate(180)
lcd.drawText('运动已结束',0,0,font,0x0f0)
lcd.drawText('该次运动时长为',0,50,font,0xff0)
lcd.drawText(str(round(gv.SportTime/60,1))+'min',0,70,font,0xff0)
lcd.drawText('该次运动减重;',0,90,font,0xff0)
lcd.drawText(str(gv.weight_int-gv.weightmin)+'kg',0,110,font,0xff0)
lcd.show()
import machine
machine.reset()
await asyncio.sleep(0.1)
# asyncio.run(TFTshow())
</code></pre>
<p> 文件:task_ESPnow.py</p>
<pre>
<code class="language-python">import network
import aioespnow
import asyncio
import neopixel
import binascii,time
from machine import Pin
from neopixel import NeoPixel
from time import localtime
from global_var import *
from cfg import *
np = neopixel.NeoPixel(Pin(8), 1)
# WLAN接口必须处于活动状态才能 send()/recv()
sta = network.WLAN(network.STA_IF) # 或 network.AP_IF
sta.active(True)
sta.disconnect() # 对于 ESP8266
e = aioespnow.AIOESPNow()
e.active(True)
peer = b'\xAA\xBB\xCC\xDD\xEE\xFF' # 配对设备wifi接口的MAC地址
e.add_peer(peer) # 发送前必须 add_peer()
# e.send(peer, '123')
def Pcvt1(x):
if x>=0 and x<0.76:
return 35
if x>=0.76 and x<0.92:
return 25
if x>=0.92 and x<1.0:
return 30
if x>=1.0 and x<1.08:
return 35
if x>=1.08 and x<1.3:
return 40
if x>=1.3 and x<1.4:
return 45
if x>=1.4:
return 50
def Pcvt2(x):
if x>=0 and x<1.12:
return 20
if x>=1.12 and x<1.19:
return 25
if x>=1.19 and x<1.24:
return 30
if x>=1.24 and x<1.26:
return 35
if x>=1.26 and x<1.28:
return 40
if x>=1.28 and x<1.32:
return 45
if x>=1.32:
return 50
def P_Vdata_int(r):# 初始电压平均
global v1,v2,m,n
print('接收的r字节数据为:',r,type(r))
if r == 49:
gv.V1 = float(r.decode('utf-8'))
print('转换后的整数数据为V1:',gv.V1)
v1 = gv.V1+v1
n=n+1
gv.Vint1 = round(v1/n,3)
print('gv.Vint1',gv.Vint1 )
elif r == 50:
gv.V2 = float(r.decode('utf-8'))
print('转换后的整数数据为V2:',gv.V2)
v2 = gv.V2+v2
m=m+1
gv.Vint2 = round(v2/m,3)
print('gv.Vint2',gv.Vint2)
def P_Vdata(x):#取峰值
global r
gv.st_cnt+=1
if gv.st_cnt<=x:
if r == 49:
gv.V1 = float(r.decode('utf-8'))
elif r == 50:
gv.V2 = float(r.decode('utf-8'))
if gv.V1 > gv.V1max:
gv.V1max = gv.V1
if gv.V2 > gv.V2max:
gv.V2max = gv.V2
else:
gv.st_cnt = 0
gv.V = gv.V1+gv.V2-gv.Vint1-gv.Vint2
print('========gv.V:',gv.V)
gv.weight = WGTcvt(gv.V)
if gv.weight<gv.weightmin:
gv.weightmin = gv.weight
def WGTcvt(y):
if y>0.1:
t = 57+y*12
elif y< 0.1 and y >0.05:
t = 57+y*21
elif y <0.05 and y>0.01:
t = 57+y*31
elif y <0.01:
t = 57+y*61
print('^^^^^^^^^t',t)
return t
async def e_recv():
global r
while True:
if e.any():
host, msg = e.recv()
if len(msg)>=5 and len(msg)<=15:
if msg[:2] == b'cc':
print('msg',msg)
np = (0, 30, 0)
np.write()
gv.int_cnt +=1
print('gv.int_cnt',gv.int_cnt)
if gv.int_cnt <= 10:#初始化时间10S
#gv.int_flag = 1
P_Vdata_int(msg)
print('gv.int_flag :',gv.int_flag)
elif gv.int_cnt>10 and gv.str_flag ==0:
gv.int_flag = 0
gv.str_flag = 1
print('gv.str_flag:',gv.str_flag)
else:
pass
if gv.str_flag == 1:#初始化结束,胎压转换
gv.P1_int = Pcvt1(gv.Vint1)
print('gv.P1_int',gv.P1_int)
gv.P2_int = Pcvt2(gv.Vint2)
print('gv.P2_int',gv.P2_int)
gv.str_flag = 2
print('gv.str_flag :',gv.str_flag)
if gv.str_flag == 2:#开始测量初始体重
print('---------------gv.str_flag :',gv.str_flag)
gv.waitcnt+=1
if gv.waitcnt>=100:
r = msg
P_Vdata(10)#取50次数据的峰值
if gv.weight>0 and gv.st_cnt ==0:
gv.weight_int = gv.weight
print('gv.weight_int',gv.weight_int)
gv.weight = 0
gv.str_flag = 3
if gv.str_flag == 3:#开始测量运动后体重
print('```````````gv.str_flag:',gv.str_flag)
r = msg
P_Vdata(20)#每100次数据更新一次体重
r = localtime()
s = '{:04}-{:02}-{:02} {:02}:{:02}:{:02} > {}'.format(r, r, r, r, r, r, str(msg))
f = open('称重数据1.txt', "a", encoding="UTF-8")
f.write( s+'\n')
f.close()
await asyncio.sleep(0.05)
np = (0, 0, 0)
np.write()
else:
print('msg head error')
else:
print(' msg len error')
else:
print('等待数据,请在5秒内启动车辆')
gv.endcnt+=1
if gv.endcnt>=100:#(5秒内未收到数据,运动结束)
gv.endcnt = 0
gv.end_flag = 1
await asyncio.sleep(0.05)
# asyncio.run(e_recv())
</code></pre>
<p> </p>
<p><span style="font-size:20px;"><b>五、</b><b>作品功能演示视频</b></span></p>
<p><a href="https://www.bilibili.com/video/BV1hLBSY8E14/">自行车智能骑行助手——得捷大赛作品演示视频_哔哩哔哩_bilibili</a></p>
<p>7cc8994b5fdfb34a822f81dc96acf77b<br />
</p>
<p><span style="font-size:20px;"><b>六、项目总结</b></span></p>
<p> 参加这次活动之前,刚好买了一辆自行车,加入了骑行者的队伍,然后计划购买一些配件来改装车辆,和同事讨论时,想到胎压测量以及骑行时的体重监测等场景,在市面上没有找到类似功能的产品,所以想尝试做一下这个功能的作品,看看是否能够实现预期的效果。</p>
<p> 此次作品将最初想法能大致实现了,可以完成测量胎压和体重等功能,但由于各方面的限制,数据精度上但是还有很多不足,例如传感器精度不足以及线性度不够、安装位置无法保证受力均衡、轮胎的自然转动带来的数据波动,都会影响最后得到的数据精度。由于时间关系,还有运动数据上传到手机功能暂未实现,这个后续再添加。</p>
<p> 通过参加本次活动,收获还是不小的,真真切切的感受到了从一个想法到一个作品做好的难度,需要考虑的东西太多了,做到一半才发现很多东西与预想的差别很大,可能写程序、画板子还是其中最容易的环节,例如此次作品中,一个合适的传感器的选择和安装固定的方式,在前期就花了很多时间。</p>
<p> 最后,感谢得捷和EEworld,组织了这次活动,让大家的想法可以有个很好的机会来实现,并且能学习到很多其他人的有趣想法。</p>
<p> </p>
<p> </p>
<p><a href="https://bbs.eeworld.com.cn/thread-1290206-1-1.html">【2024 DigiKey 创意大赛】物料开箱帖 - DigiKey得捷技术专区 - 电子工程世界-论坛</a></p>
<p><a href="https://bbs.eeworld.com.cn/forum.php?mod=viewthread&tid=1299906&page=1&extra=#pid3376376">【2024 DigiKey 创意大赛】自行车智能骑行助手 - DigiKey得捷技术专区 - 电子工程世界-论坛</a></p>
<p> </p>
<div></div>
<p><a href="https://gitee.com/newhor/zhinengqixing.git" target="_blank">https://gitee.com/newhor/zhinengqixing.git</a></p>
<p> </p>
<p>楼主分享的技术知识非常有实用价值,值得收藏学习借鉴</p>
<p>这压力检测直接放进去了?不漏气吗? </p>
秦天qintian0303 发表于 2024-11-26 12:17
这压力检测直接放进去了?不漏气吗?
<p>可能传感器是夹在内胎和外胎之间</p>
wangerxian 发表于 2024-11-26 18:47
可能传感器是夹在内胎和外胎之间
<p>先将轮胎气放光,然后夹在钢圈和内胎之间,然后再充气固定的 </p>
Newhor 发表于 2024-11-26 21:30
先将轮胎气放光,然后夹在钢圈和内胎之间,然后再充气固定的
<p>原来如此,这样确实也能检测胎压,不过在骑行的过程压力会变化吧</p>
wangerxian 发表于 2024-11-27 09:02
原来如此,这样确实也能检测胎压,不过在骑行的过程压力会变化吧
<p>分为了两个阶段,初始未骑行的状态的测量值转换为胎压,骑行中的值转换为体重,用了一些算法对进行骑行时的数据进行滤波处理</p>
Newhor 发表于 2024-11-27 09:30
分为了两个阶段,初始未骑行的状态的测量值转换为胎压,骑行中的值转换为体重,用了一些算法对进行骑行时 ...
<p>明白了,还能变相的称体重,想法确实很好。</p>
页:
[1]