NRF51822+MPU6050 蓝牙加速度传感器的功耗控制

上个月(Jul 2019)在公司做一个 Hackathon 项目,想的是给 PC 的屏幕加上重力感应旋转,就和手机平板一样,选择了低功耗蓝牙(BLE)获取加速度信息(可推出重力方向)。在 Windows 上写了后台程序https://github.com/hcwg/ScreenFerris,跑的很成功。演示给整个公司的同事看反响也可以。但是有一个严重的问题,就是一节 CR2032 纽扣电池只能支撑5天左右。续航严重不足。

我用的传感器是淘宝购买的,NRF51822 主控 + MPU6050 加速度计和陀螺仪,还有我用不到的环境光传感器和温度传感器。生产商是讯联电子(infor-link),产品名是 NRF51 Sensor Tag,此为硬件背景。

我显然知道现在低功耗蓝牙有多厉害,大部分设备都能用一节 CR2032 电池续航一年,显然是厂商附带的程序和我的需求不契合,导致续航不够长。于是我决定自己写一下代码,反正 ARM 的程序也不是第一次写,能有多难?在苏州用掉了两周的晚上大概搞出来了,现在续航还在测试,不过看起来电压都不怎么下降的样子。

硬件圈子分享的气氛不太浓,我就大概写下自己的心得,希望能帮助到别人。

1.减少无线电发送

我的需求很简单,就是把 Sensor 贴在显示器上,然后当显示器旋转的时候 传感器 能告诉 PC 加速度信息就行。原厂的程序没给源码,能干的事情就是由 PC 监听(Enable Notify) 传感器某个 Service 的某个 Characteristic, 每隔一秒由传感器通知 PC 当前的加速度信息。

对与这类低功耗MCU,平常运行电流都是 1-40μA 级别,但是电磁波辐射必然携带能量,所以发射信号的时候电流可达2-5mA左右,而且发射耗能是不可能避免的。所以减少发射次数显得至关重要。的讯联电子的出厂固件倒是提供了一个修改频率的接口,但是这个固定频率并不是最优的,我们演示的时候用的是 1s 间隔,如果提高间隔则响应速度会变慢,降低间隔则功耗不可接受。

我选择的方案是在片上对加速度信息做处理,计算相对上次发射的旋转角度,如果大于5°才发射,并且之后以 200ms 的间隔连续发射5次以应对连续旋转或者其他需要精度和稳定性的需求,此外每隔 10s 或者更长也发送一次以确保能保持活跃。

2.启用 NRF51822 上的片上 DCDC 模块

电池电压对于我们需要的无线电发射有点高,启用 DCDC 可以降低天线放大器的电压,降低功耗,出厂固件虽然没有源码,但是我通过调试接口确认了 DCDC 相关寄存器的设置,确定原厂固件没有启用 DCDC。

待续

3. 启用 MPU6050 的低功耗模式

MPU6050 上有加速度传感器,陀螺仪(角加速度传感器),和温度传感器。全部开启的电流约为 3.2mA。MPU6050 很贴心的提供了低功耗模式,如果只需要加速度,可以屏蔽其他功能并由 MPU6050 自行周期性睡眠,不需要 MCU 干预。在 1.25 Hz 的唤醒频率下电流为 10μ安,我采用了 5Hz的频率,电流仅为 20 μA。是原始模式的百分之一。

待续

总结

其实 MPU6050 的低功耗模式是最重要的,前两个方法加上之后都没什么明显的功耗降低。MPU6050 低功耗模式开启后效果立竿见影。

USB低功耗蓝牙(BLE)适配器选购

最近要做一个使用低功耗蓝牙 (Bluetooth LE, BLE) 的项目。大部分笔记本都自带蓝牙,而且蓝牙版本很高,都到5.0了。手机更是不用说,13年以来的手机大部分都支持BLE。奈何大部分台式机都没有蓝牙适配器,必须买一个USB的适配器。

很可惜,去京东上搜索蓝牙适配器,绝大部分都是 4.0 的,而且也不标注是否支持BLE,只能自己找了。

注意,想要使用低功耗蓝牙,必须使用Windows 8以上版本,Windows 10 肯定是没问题的。

首先是 CSR8150 方案,市面上绝大部分USB的蓝牙适配器采用这个方案,估计是因为真的便宜,没有标注只能买回来看。高通的CSR8150介绍页面写的很清楚。

Bluetooth Technology: Bluetooth Low Energy, Dual-mode Bluetooth

我在京东上买了一个售价20人民币的。买回来要装一个400M左右的驱动先不说,根本没有 BLE 枚举器

CSR8150 设备管理器

既然CSR8150不行,剩下的最多的就是 BCM20702。 商家宣传好处很多,比如支持苹果(我没测试),Windows 10 免驱之类的,单凭 Windows 有驱动这一点就比 CSR 8150 强多了。淘宝买了一个30人民币的适配器。

买回来之后,很不幸,无法工作。此设备无法启动(代码 10)
一开始我还以为买到了经过多方查找,发现好像蓝牙适配器之间会有冲突,我的笔记本的 intel 网卡已经自带了 BLE 功能,会导致 USB 蓝牙适配器不能工作,而之前的 CSR8150 因为没有 BLE (也许是机智的驱动见到我机器有BLE就把BLE屏蔽了?)所以没有冲突。

果断禁用 Intel 的网卡试试。

大功告成。尝试用 Windows 蓝牙 API 调用 BLE 相关功能,完全能用。

快速求约数(divisor)

一个通常情况足够快,且好写的求约数算法

算法描述

首先求质因数,复杂度为 sqrt(最大质因数) 将原数分解为多个质因数的乘积后,求约数的方法也很简单。 每各个不同 的质因数,将该质因数的不同次方和不含该质因子的已知约数相乘,合并到结果集中。

如分求 36 的约数,首先分解为 2 × 2 × 3 × 3

  1. 初始化,集合中仅有 [1] 一个约数
    此时结果集为[1]
  2. 将 2 和 4 分别和 [1] 相乘 得到 [2, 4], 合并
    此时结果集为[1, 2, 4]
  3. 将 3 和 9 分别和 [1, 2, 4] 相乘 得到 [3, 6, 12, 9, 18, 36] 合并
    此时结果集为[1, 2, 4, 3, 6, 12, 9, 18, 36]

实现

分解质因数和后面的过程可以合并执行,不需要记录质因子,最终代码如下

GDB 调试极简入门

GDB 是 GNU 提供的流行Debug 工具,GDB 可以对任何可执行文件进行Debug

使用GDB 启动程序

最简单的方法

gdb ./a.out

如何需要使用命令行参数,则需要使用 --args 参数(下面 someargs 和 arg1 等都只是例子)

gdb –args ./a.out someargs –arg1 value1 –args2 value2

进入GDB后使用 r 开始执行程序
当程序运行遇到关键错误时,GDB会捕获错误并开始调试

当然,最重要的一点,gdb 使用 quit 命令或者 Ctrl + D 退出

使用 Core Dump 调试程序

Linux 支持在程序异常退出时使用 CoreDump 将现场内存保存在磁盘上(文件名默认为core). GDB 可以利用CoreDump文件恢复现场调试。

如果想使用CoreDump,首先要用ulimt 打开 CoreDump 功能

ulimit -c unlimited

这样程序异常退出时才会保存 core 文件。调试时使用

gdb ./a.out core

来启动调试

编译,加入符号信息

gdb 如果不能在可执行文件中找到符号(symbol)信息,调试时功能会受到极大限制,不能方便的使用断点, 不能查看代码,不能定位到崩溃的具体文件和行数。如果想要使用 GDB 调试,一般需要在编译时加上 -g 参数,加入符号信息。

gdb a.cpp -g

如果调试时发现有些变量不能输出,可能是 编译优化级别太高导致。仅在有必要的情况下,可以加入-O0 (注意是大写英文字母O 和阿拉伯数字0)选项

g++ a.cpp -g -O0

常用指令

当触发断点,或者异常中断时,GDB会切换到相应的栈帧 (frame) 等待用户调试

常用的调试指令有

  • bt (backtrace) 打印当前的调用栈
  • p (print) 打印变量内容
  • f (frame) 切换栈帧
  • l (list) 打印当前代码
  • r (run) 从头开始执行
  • c (continue) 继续执行
  • b (breakpoint) 插入断点
  • d (delete) 删除断点

用法在下面介绍

演示

下面给一个含有错误样例代码,演示 GDB 常用的调试命令

g++ debug.cpp -g -O0
gdb ./a.out

显然这段代码会产生除0错误, 先执行(输入r)gdb 输出如下

我们能看到很多信息, 比如错误出现在 debug.cpp 文件的第3行,这行的代码是 return x / y;, 出现的错误是算数错误(Arithmetic exception)

现在可以使用××打印变量××(p 命令)功能,看看 x 和 y 到底是什么

我们知道了,错误是因为 y = 0,出现了除 0 错误

那么为什么会执行这段代码呢?可以使用 list 命令查看附近的代码. 需要指出的是,list 默认向下浏览代码,你可以使用l - 向上浏览代码

到这里我们知道了,y 是 0 的原因 divide 函数的输入参数 y 是 0,那么是谁调用了这个函数呢?我们可以使用 查看调用栈 功能 (bt命令)

我们可以看出,是debug.cpp 的第 6 行 main 函数调用了 divide 函数,还能看到divide 函数的实参是 x = 10, y = 0.
此时我们应当使用切换栈帧 (frame命令) 回到 main 函数查看调用 divide 的代码. #1 代表main 函数的栈帧编号为 1

我们通过切换栈帧和打印代码看到,传给 divide 函数的 y 参数是 循环变量 i. 我们可以打印此时 i 的值

print 指令不仅可以查看变量的值,还能进行简单的运算,例如对vector的元素的访问,可以直接 p vec[10] 这样操作。

最后我们尝试下断点 (breakpoint). 只要gdb等待你的输入,你就可以添加断点,比如我们给 main 函数的 第6行添加断点并重新运行

GDB 的确帮我们中断了程序并进入调试,此时我们可以继续(continue)

断点不是一次性的,再次执行代码会再次触发断点。此时我们可以删除断点(d),需要指出断点编号

最后,退出GDB (quit)

解决Driver/library version mismatch

服务器更新nvidia driver 版本之后,经常会出现

这个问题出现的原因是kernel mod 的 Nvidia driver 的版本没有更新,一般情况下,重启机器就能够解决,如果因为某些原因不能够重启的话,也有办法reload kernel mod。

简单来看,就两步

  1. unload nvidia kernel mod
  2. reload nvidia kernel mod

执行起来就是

  1. sudo rmmod nvidia
  2. sudo nvidia-smi

nvidia-smi 发现没有 kernel mod 会将其自动装载。

但是事情远远不是这么简单,一般情况下都会遇到卸载失败。

这时,就要一点一点的卸载整个驱动了,首先要知道现在kernel mod 的依赖情况,首先我们从错误信息中知道,nvidia_modeset nvidia_uvm 这两个 mod 依赖于 nvidia, 所以要先卸载他们

可以看到 nvidia 被使用了152词,我们可以先卸载 nvidia_uvm 和 nvidia_modeset

先查看下有哪些进程使用了 nvidia*

这些进程有个了解,如果一会卸载失败,记得关闭相关进程。

卸载

再 lsof 一遍,如果 nvidia 的使用 Used by 还没有降到 0,kill 相关进程

最后

收工

CheckAC 在Chrome WebStore 上架

2013年暑假集训,我们(@yangz @alwa)发现了一个小众的需求,即在OJ上做题的时候不能确定一道题自己有没有过掉,尤其是在查看别人的提交记录的时候。

下载地址:CheckAC

Github(欢迎提交意见,代码更好):CheckAC

还有,不知道为什么,我们的集训队一直比较关心一个人的过题数量,于是乎我们都在乎了。

(说来惭愧,暑假之后几乎没怎么搞算法,全都是搞应用了,最近虽然考试多,还是要多刷刷题了,太少了看不过去)

于是,我们就决定开发一款Chrome 的扩展程序(插件),能辅助我们在OJ上做题,想法很好,说干就干。

开发速度简直快的惊人(凭记忆):

2013-7-19 14:00 ~ 2013-7-21  2:40 :

36小时速成0.1版,可惜当时还不会用Github ,没有记录下开发过程

  • 当天自学了如何制作插件
  • 当天晚上搞出了可以在POJ网站上抓取过题数量和通过题目列表的正则表达式
  • 学习了一个东西叫XMLHttpRequest ,知道了这东西能在插件里异步访问Web,后来知道了一个东西叫Ajax
  • 学习了一个叫localStorge的本地存储方法作为插件的存储
  • Chrome 插件有个东西叫ContentScript,能在目标网页中运行,能完成打钩的功能
  • Chrome 插件有个BackGroundPage 通过它我才能读取插件能访问的localStorge 并和ContentScript通信,来告诉ContentScript 那些题目我已经AC
  • 用Photoshop 画了个LOGO
  • 21日凌晨抄袭了”印象笔记剪藏”的CSS样式,然后完成了0.1版

不过这个东西的功能仅限于抓AC列表,点插件输入个POJ题号能用绿色和红色表示你题目有没有AC,发下21日凌晨怀着激动的心情在集训队内部论坛发布时的截图:

CheckAC Alpha 屏幕截图

之后的几天:

  • 听说有个东西叫BootStrap 是个前端框架,用上之后果然非常好使,用它写了Setting 和 popup页面
  • 听说有个东西叫Jquery,是个JS库,用上之后果然非常好使
  • 搞定了ZOJ 的题目抓取功能
  • 学了一个东西叫JSON,能序列化对象,终于不用我人工定义localStorge中字符串的格式了
  • ACM-ICPC信息站找了个近期比赛的JSON源,在此表示感谢

现在,CheckAC 已经有了关注他人,ToDoList 等功能。欢迎大家使用。

为什么现在才上架呢?因为Google WebStore开发者账户要用$5验证,要有信用卡,而且不能是中国大陆的。于是鼓动我父亲给我办了张卡,拿到卡之后非常顺利,2小时就完成了从开发者账户验证到上架的过程,在此也给想在ChromeWebStore上架的朋友说一下我的操作。

  1. 招商银行美国运通(AmericaExpress)双币信用卡
  2. Goagent+Chrome隐身模式登陆Google账号
  3. 填写的是香港地址
  4. 中国正常手机号码

迁移至新域名(comzyh.com)啦

今天下午tk域名解析莫名其妙不正常,感觉tk这个小岛国还是有点不靠谱,于是乎迁移至com域名.

目前301重定向什么的都弄好了,就等Google收录了.用以前的tk域名还能够继续访问,会自动跳转过去的.

正好也试试Word2013直接用Xmlrpc发布文章的功能.

感谢大家支持J

等比数列求和的二分法

常见问题:给定整数M,等比数列\(\{{a_n}\}\),求其前n项和对M的模

朴素的想法,我们可以用等比数列求和公式\(S_n=\frac {a_1(1-q^n)}{1-q}\)求得和再模除M,但是计算过程中取模导致分母下面的1-q不能直接除,要求1-q的乘法逆元;

这里介绍另一种思路,就是使用二分的思想,很像矩阵快速幂
首先,我们先讨论一个简单的问题:
求\(a^1+a^2+a^3+a^4+a^5+a^6+a^7+a^8\)的和
提取公因式,我们能把原式变为
\((a^1+a^2+a^3+a^4)(1+a^4)\)
进一步变换为\((a^1+a^2)(1+a^2)(1+a^4)\)
\(=a(a+a^1)(1+a^2)(1+a^4)\)
我们把这种等比数列前\(2^k\)项和记为\(S(k)\)有递推式\(S(k)=S(k-1)(1+a^{2^{k-1}})\)
这是二分法的关键,也就是说我们能在log(n)的时间内求出等比数列前\(2^k\)项的和

当我们要求的不是前2的整数次幂的时候,我们将n分解为2的整次幂的和:
比如求前等比数列11项的和,我们将11(二进制表示:1011)分解为1+2+8(二进制表示1+10+1000)
前11项的和可以表示为:
\((a^1)+(a^2+a^3)+(a^4+a^5+a^6+a^7+a^8+a^9+a^{10}+a^{11})\)
可进一步表示为
\(a^0(a^1)+a^1(a^1+a^2)+a^3(a^1+a^2+a^3+a^4+a^5+a^6+a^7+a^8)\)
即:\(a^0S(0)+a^1S(1)+a^3S(3)\)
Well done!我们又把原式分解成了多个\(a^xS(y)\)的和
Continue reading