Eli Billaue的博客。这个算法的大意就是遍历所有数据,记录遇到的最大值。假设新遇到的点小于最大值减阈值,就意味着先前遇到的最大值是峰值。这也是为什么上图中最后一个峰值没有被这种方法找到:因为它需要差距足够大的后续值(大过Δ),才能确定前面遇到的是峰值——这对我们来说是好事,想象你在等红灯时磁铁正好在ICM-20948旁边,造成持续偏高的读数。那时你一定不想让其中任意一个读数被当成转了一圈才应该出现的峰值。
这条StackOverflow也讨论了峰值检测的算法,我觉得相比Eli Billaue的方法有些复杂,所以没有采用。
其实我最开始想到的检测曲柄转圈的方法不是检测峰值,而是检测上升边。大概方法是记住前面的最小值,如果此时碰到了比最小值大大约½个峰谷值的点,则记为一次上升。
从需要手动输入一个值的角度来看,这方法看起来和Eli Billaue的峰值检测算法有些相似。但输入的值恰好要½个峰谷值:大了有可能检测不到上升边;小了又可能把一条边当好几条边。Eli Billaue的方法就对要输入的值(Δ)没什么限制,因为他的方法在检测到峰值以后,会去寻找谷值,找到谷值才会再找峰值。而判定谷值这个动作是在遇到比最小值大Δ的后续点时才完成的,也就是说,在真正走过波谷之前不会再检测峰值。所以,Eli Billaue需要的Δ不用特意设成½个峰谷值,因为他的算法强调了两个波峰之间一定有个波谷(假设找完峰值不找谷值直接找下一个峰值,就需要设成½个峰谷值了)。
踏频的单位是圈每分钟(rpm),所以最正确的计算方式当然是维护一个过去一分钟峰值时间点的队列,队列长度就是踏频。不过这种方法不能很好地反映突变情况——想象维持59秒高踏频,在最后一秒突然慢了下来的情景。
那只记录前一秒的数据可以么?我们可以在每次达到峰值时给steps加一,然后设置一个每秒的时钟中断,在中断处理程序里根据steps计算踏频,再清零steps。其实也不行,因为我只在牙盘的一个点放有磁铁,这一秒能检测到的要么没转圈,要么转一圈(我估计我不会有120rpm的踏频)。所以计算出的踏频一定只有0或60rpm。
给牙盘上平均放置n块磁铁,可以在一秒内检测到最小1/n的转动。我个人觉得这样比较麻烦,所以还是只保留一块(叠加的)磁铁,用上一次和这次峰值之间相距多少微秒,来计算保持这个节奏一分钟的踏频。
具体地说,是在每次峰值时调用utime.ticks_ms
,再用utime.ticks_diff
得出时间差。不过这有个问题:utime.ticks_ms
只能返回0到TICKS_PERIOD区间的值,utime.ticks_diff
也只保证差距在TICKS_PERIOD/2之间才有效。那TICKS_PERIOD到底是多少?文档说:
The wrap-around value is not explicitly exposed.
我尝试从源码里推导出TICKS_PERIOD,只能确定它是MICROPY_PY_TIME_TICKS_PERIOD。但具体是多少,我没法拿肉眼看,还是等哪天需要自个编译MicroPython时加个printf来看吧。不过,经测试,一两秒的间隔还是容得下的。
Pico W的W,是Wireless的W。这代表它既支持Wi-Fi,又支持蓝牙。根据官方的教程,很快就可以用Pico W进行蓝牙advertise、notify。官方的示例程序基本可用,我们需要改的只有两项:
前者需要上蓝牙的官方仓库里找。其中service id好找,搜cycling就能找到org.bluetooth.service.cycling_speed_and_cadence及其对应的0x1816;我是死活找不到——后来才发现他们在列举characteristic时把cycling_speed_and_cadence简写成CSC了,所以我要找的是对应0x2A5B的CSC Measurement。
编号有了,接下来确定要传输多少字节。由于人类踏频不会超过256rpm,所以长达一字节的无符号数即可容纳踏频信息。
发完了,谁来收呢?Chromium系的浏览器实现了实验性的Web Bluetooth API,允许你通过JavaScript来操纵蓝牙配对、消息传递。Chrome的开发者还分享了示例程序。我把那段程序改了改,让它在收到蓝牙通知时打印当前时间和转速。下面是演示视频:
这段不小心把我的传家宝拖鞋也录进去了。本来想重录一遍更漂亮的,电池却在关键时刻没电了——那是我最后的七号电池了!
我的代码是一刻不停地读取数据,也许可以适当降低频率来减少功耗,但目前阶段还是买新电池麻烦更少。
我查了下哪家充电电池又便宜又好,发现大家比较推荐宜家的LADDA(听起来很像俄罗斯破烂汽车)。正巧我想买张桌子,就找了个周末上宜家扛回来了一张桌板、四条桌腿和四节电池。
回到家,给Maker Pi Pico Mini安上新电池。通电就亮的小LED马上点亮——又马上熄灭。奇了怪了,同样的电池,抠下来给附带屏幕的Badger 2040 W(也是在背后焊了一个Pico W)换上就能照常使用;给Maker Pi Pico Mini接上连灯都亮不了。
从Badger 2040 W的教程上看到,两节充电电池的电压(各1.2V)对Pico W的无线功能来说不够。可是实际测试时发现,两节LADDA完全可以让Badger 2040 W连上Wi-Fi。我问了两个相关专业的朋友,西工大的告诉我可以把两个七号电池盒串联起来,用LDO、DCDC boost把电池盒的电压规定在某个区间;哈工大的则推荐我去买个小一点的移动电源,他说移动电源把哪些工作都做好了。
为什么2.4V的电压能带动有屏幕的Badger 2040 W,却点不亮没屏幕的Maker Pi Pico Mini?我怀疑是后者电路设计有问题。当然,这就不是我能理解的了。我对这个问题的解决方式是:买一个三块电池的电池盒,在到货之前给LADDA充满电再接到Maker Pi Pico Mini上。
我的原计划是把Badger 2040 W装在车把上,实时显示我的踏频。不过安卓上的Chrome浏览器也支持Web Bluetooth API,所以只要把我的测试HTML传到VPS上,再用手机打开就可以显示、记录踏频了。实际测试时担心了一路,害怕自己没把各个部件固定牢:掉到地上还好,要是被卷进齿轮里可就不好了。好消息是:一路下来有惊无险,也得到了足够的数据——可以说,我的自制踏频器已经通过阶段性成功了。
虽说目前已经可以使用,但还有许多改进的空间。其中最重要的就是外壳——有了外壳,我就不用担心下雨了,而且安装、拆卸时也要方便许多。目前我得知图书馆里有3D打印机,我也短暂地学习过《自动桌子 发明家
有了外壳,就可以随时使用了。等数据多了应该会微调读取磁强计数据的间隔、发送蓝牙消息的时机等等。也说不定会利用ICM-20948的陀螺仪和加速计去测量些别的什么东西——我可以用这两个来推算出当前速度么?如果能推出速度,想必连里程都可以估算出来。
另外,我还有一块BME688。之前尝试过用它的气压计估计当前海拔。也许会把它放到自行车上,看看我什么时候能累计爬完一个珠峰。
其实,能玩的很多,只是缺少连续的大块时间。
复制以下链接,并粘贴到你的Mastodon、Misskey或GoToSocial等应用的搜索栏中,即可搜到对应本文的嘟文。对嘟文进行的点赞、转发、评论,都会出现在本文底部。快去试试吧!
链接:https://emptystack.top/note/cadence-sensor
注:点击昵称可以查看对评论的回复。