拉普拉斯矩阵及基尔霍夫矩阵树定理

1 Laplacian matrix

1.1 简介

  Laplacian matrix又名Kirchhoff matrix,是一种图的矩阵表示方法,本节主要讨论Laplacian matrix的几种简单应用。

1.2 定义
1.2.1 无向图

  给定一个有$n$个顶点$m$条边的无向图图$G=(V, E)$,则该图的Laplacian matrix$L:=(l_{i,j})_{n\times n}$,定义为:
$$
L=D-A
$$
  其中$D$表示$G$的度数矩阵,$A$表示$G$的度数矩阵。

1.2.2 有向图

  在有向图中,由于出入度的引入,Laplacian matrix可以被表示为如下形式:
$$
L=\left\{
\begin{aligned}
&deg(v_i) && if\ \ i = j\\
&-1 && if\ \ i \neq j\ and\ v_i\ is\ adjaccent\ to\ v_j\\
&0 && otherwise\\
\end{aligned}
\right.
$$

1.2.3 Symmetric normalized Laplacian

  另一种常用的Laplacian matrix为其正则标准型:
$$
L^{sym}:=D^{-\frac{1}{2}}LD^{\frac{1}{2}}=I-D^{-\frac{1}{2}}AD^{\frac{1}{2}}
$$
  也可以表示为:
$$
l^{sym}_{i,j}=\left\{
\begin{aligned}
&1 && if\ \ i = j\ and\ deg(v_i)\ne 0\\
&-\frac{1}{\sqrt{deg(v_i)\times deg(v_j)}} && if\ \ i \neq j\ and\ v_i\ is\ adjaccent\ to\ v_j\\
&0 && otherwise\\
\end{aligned}
\right.
$$

1.3 性质
1.3.1 Laplacian matrix是半正定的

  考虑图$G$的关联矩阵$M_{m\times n}$,其中边$e$(包含了顶点$i$和顶点$j$,且$i>j$)和点$v$构成元素$M_{ev}$
$$
M_{ev}=\left\{
\begin{aligned}
&1 && if\ v = i\\
&-1 && if\ v = j\\
&0 && otherwise\\
\end{aligned}
\right.
$$
  则可由此算出其Laplacian matrix:
$$
L = M^TM
$$
  这是由于$M^T$的一行或$M$的一列表示其中一个顶点$v$与边集$E$的关系,当$v$相同时,与之相连的边的平方为1,相加和为度数,当$v$不同时,若这两点若通过一条边相连,其对应$e$位置的乘积为-1,否则为0,这正是Laplacian matrix的定义。

  下面考虑$L$的特征值$\lambda_i$,其对应的单位特征向量为$v_i$,则有
$$
\begin{align}
\lambda_i &= v_i^TLv_i\\
&=v_i^TM^TMv_i\\
&=(Mv_i)^T(Mv_i)
\end{align}
$$
  由于$(Mv_i)^T(Mv_i) \ge 0$,因此$\lambda_i \ge 0$。

  所以$L$的任意一个特征值都是非负的,则$L$是半正定的。

1.3.2 Laplacian matrix任意一行、一列的和为0

  由$L$的定义,其对角线上的元素为节点的度,其余元素的和为与其相连的节点数的个数的相反数,二者相加为0。

1.3.3 Laplacian matrix有一个特征值为0

  由性质1.3.2,取向量$v_0=(1, 1,…,1)$,有$Lv_0=0$,且有$Lv_0=\lambda_0v_0$,当$\lambda_0 = 0$时成立,所以$L$存在一个零特征值,这也同时证明了$L$是奇异的。

1.3.4 Laplacian matrix任意两个代数余子式的值都相等

  首先我们证明,代数余子式$A_{i,j}$与$A_{i,k}$($j \ge k$)的值相等,即证$(-1)^{i+j}|A_{i,j}|=(-1)^{i+k}|A_{i,k}|$,即
$$
(-1)^{j-k}|A_{i,j}|=|A_{i,k}|
$$
  将$A_{i,j}$中的所有列都加到第$k$列上,得到矩阵$A_0$,取出其第$k$列得到列向量$\vec{k}$,由性质1.3.2,$\vec{k}$为除去$L$中第$j$列后所有列相加的和,即有
$$
\vec{k}+\vec{j}=0
$$
  即$\vec{k}=-\vec{j}$,将$A_0$的第$k$列取相反数,得到$A_1$,则$A_1$的第$k$列与$L$的第$j$列相等,且由行列式的性质$|A_1|=-|A_{i,k}|$

  下面我们将$A_1$中第$k$列的值向右对换,直到这一列处于$A_{i,k}$中第$j$列的位置,则这个过程经过了$j-k-1$次对换,这时我们得到$A_2=A_{i,k}$,则$|A_{i,k}|=|A_2|=(-1)^{j-k-1}|A_1|=(-1)^{j-k}|A_{i,j}|$,证毕。

  完全类似地,我们可以得到,代数余子式$A_{i,j}$与$A_{k,j}$的值相等。

  由以上两个结论,不难证明$L$的任意两个代数余子式的值都相等。

2 Kirchhoff’s matrix tree theorem

2.1 定理内容

  无向图的生成树个数等于其Laplacian matrix的任意一个代数余子式的值。

2.2 证明
2.2.1 引理——柯西-比内公式

  设矩阵$A_{n\times m}$,$B_{m\times n}$,我们定义$[m]$是集合${1, …, m}$,规定$(\begin{matrix}[m]\\ n\end{matrix})$表示$[m]$的所有大小为$n$的子集组成的集合。

  对于$S\in(\begin{matrix}[m]\\ n\end{matrix})$,记$A_S$为$A$中列指标位于$S$中的子矩阵,类似定义$B_S$为$B$中行指标位于$S$中的子矩阵,则有:
$$
det(AB)=\sum_Sdet(A_S)det(B_S)
$$
  该定理证明比较复杂,不在本文讨论范围内,可查阅参考资料[2]。

2.2.2 Kirchhoff’s matrix tree theorem

  设$G$为不具有重边和自环的无向图,由1.3.4,我们只需要证明$L$的一个代数余子式$A_{1,1}$能够使定理成立成绩,使用引理2.2.1,得到:
$$
\begin{align}
det(A_{1,1}) &= det(M_1M_1^T)\\
&=\sum_S det(M_{1,S})det(M_{1,S}^T)\\
&=\sum_S det(M_{1,S}M_{1,S}^T)\\
&=\sum_S det(M_{1,S})^2
\end{align}
$$
  其中$S\in(\begin{matrix}[m]\\ n-1\end{matrix})$,$(\begin{matrix}[m]\\ n-1\end{matrix})$包含了所有选出$n-1$条边的方案(边集),如果这$n-1$条边是连通的,则其生成图一定为一棵树。下面,我们只需证明

  1. 当这$n-1$条边不连通,$det(M_{1,S})=0$
  2. 当这$n-1$条边连通,$det(M_{1,S})=\pm 1$

  设这$n-1$条边不连通,考虑$M_{1,S}M_{1,S}^T$,其为$G$以$S$为边集的导出子图的Laplacian matrix$(L)$去掉第一行第一列得到的代数余子式,使第一行第一列保持原先位置的前提下交换$L$的行列,使新矩阵的连通分支分块并依次排列在对角线上,显然非对角线元素的连通块之间没有连边,矩阵为分块对角阵:
$$
\left(\begin{matrix}
T_1 &\dots &0\\
\vdots &\ddots &\vdots\\
0 &\dots &T_p
\end{matrix}\right)
$$
  则对任意的$i$,$T_i$均为其连通子图的Laplacian matrix,行列式为0,且$p \ge 2$,故即使去除$L$的第一行第一列,行列式依然为0。

  设这$n-1$条边连通,使用数学归纳法:

  当$n=2$,则显然有$det(M_{1,S})=\pm 1$。

  假设对于$n-1$,$det(M_{1,S})=\pm 1$成立,此时所构成图的Laplacian matrix为$L$。

  设新加入点的标号为$n$,其加入在了标号为$u$的节点上。在边数为时$n-1$,有$det(M_{1,S}M_{1,S}^T)=det(M_{u,S}M_{u,S}^T)$,则新图的Laplacian matrix $(L’)$的去掉第$u$行第$u$列的代数余子式可以表示为:
$$
A’=
\left(\begin{matrix}
M_{u,S}M_{u,S}^T &A\\
B &1
\end{matrix}\right)
$$

  此时,$n$与左上角矩阵中的任何点均没有连线,所以$A、B$均为0矩阵,则有$det(A’)=det(M_{u,S}M_{u,S}^T)$,所以$L’$具有与$L$相同的代数余子式

  至此,我们归纳证明了这$n-1$条边连通时,$det(M_{1,S})=\pm 1$,同时也证明了当$G$为一棵树时,其Laplacian matrix的代数余子式值为$\pm 1$。

  综上所述,当图$G$的生成子图为树,$det(M_{1,S})^2=1$,否则$det(M_{1,S})^2=0$,也即对于任意的$S$,只要$S$中的边能构成树,其对总和贡献1,否则不计数,所以$\sum_S det(M_{1,S})^2=det(A_{1,1})$即为生成树的个数,故无向图的生成树个数等于其Laplacian matrix的任意一个代数余子式的值。

2.2.3 推广

  我们规定了$G$为不存在重边与自环的无向图,下面进行对有重边和自环的普通图的推广。

  自环:自环不可能对结果产生影响,假如原图存在自环剔除即可。

  重边:重新定义Laplacian matrix为如下形式:
$$
L=\left\{
\begin{align}
&deg(v_i) &&if\ i=j\\
&-t &&if\ i\ne j\ and\ v_i\ is\ adjaccent\ to\ v_j\ by\ t\ times \\
&0 &&otherwise
\end{align}
\right.
$$
  显然这样的定义不影响Laplacian matrix的性质,以上命题均可轻易得证。

  有向图:有了上述定义,则有向图的Laplacian matrix也具有相应的性质,命题也可轻易得证。

参考文献

[1] Godsil, C.; Royle, G. (2001). Algebraic Graph Theory, Graduate Texts in Mathematics. Springer-Verlag.
[2] 642:550, Summer 2004, Supplement 3;The Cauchy-Binet formula.
[3] Chung, Fan (1997) [1992]. Spectral Graph Theory. American Mathematical Society. ISBN 978-0821803158.
[4] Chaiken, S.; Kleitman, D. (1978). “Matrix Tree Theorems”. Journal of Combinatorial Theory, Series A. 24 (3): 377–381. doi:10.1016/0097-3165(78)90067-5. ISSN 0097-3165.
[5] Tutte, W. T. (2001), Graph Theory, Cambridge University Press, p. 138, ISBN 978-0-521-79489-3.

量子霍尔效应初探

摘 要

  霍尔效应于1879年被Edwin Hall首次发现,量子霍尔效应是的霍尔效应量子化的体现,量子霍尔效应主要分为整数和分数两种类型,这两种类型的量子霍尔效应分别于1985年和1998年获诺贝尔奖,理论物理学家Chetan Nayak指出,由量子霍尔效应产生的非阿贝尔态可能成为实现拓扑量子计算机的基础[1]。然而由于量子霍尔效应的产生需要强磁和极端温度的加持,将其实际应用存在着巨大困难,2013年,薛其坤院士带领其团队发现了量子反常霍尔效应,使在零磁场条件下应用量子霍尔效应成为可能,这或许能被应用于制作低能耗的电子设备,将极大提高计算机逻辑运算单元的处理能力。本文讲主要探讨整数量子霍尔效应的成因与新千克单位制的定义。

关键词:量子霍尔效应;整数量子霍尔效应;国际千克单位制

1 量子霍尔效应

1.1 经典霍尔效应

  考虑将固态导体置于磁场中,当有垂直于磁场的电流通过时,载流子收到洛伦兹力产生偏移,并在此方向上产生电场,以此影响后续载流子并使之受力与磁场产生的洛伦兹力平衡,这就是经典霍尔效应,由此产生的内建电压称为霍尔电压。如图1,假设载流子为电子,在导体内有沿$x$轴正向的电流$I$ ,沿z轴正向的磁场 ,霍尔效应将产生电场$E$,生电压 $V_H$

图1

  则此时电子受力为:
$$
\vec{F_e}=q(\vec{E}+\vec{v}\times \vec{B})
$$
  在平衡状态下,$\vec{F_e}=0$,有$0=E_y-v_xB_z$,若导体宽为$l$,高为$d$,则$lE_y=V_H$,综上得到
$$
V_H=v_xB_Zl
$$
  导体中电流和$l$的关系为
$$
l=n(-e)sv=n(-e)dl(-v_x)=nedlv_x
$$
  代入,得到
$$
V_H=\frac{I_xB_z}{ned}
$$
  由此可见,磁场场强或流通电流越大,霍尔电压越大,即霍尔电压与磁场强度成正比。

1.2 整数量子霍尔效应的发现

  朴素的霍尔效应是在三维空间中电子的运动情况,电子可以在导体中自由移动,物理学家通过各种手段将电子限制在了二维平面内,我们称之为电子气。1980年,K. von Klitzing等人在研究“金属-氧化物-半导体场效应晶体管”在低温强磁场的作用下的霍尔效应时,发现了下图的结果

图2

图3

  在图2图3中,随着场强的增加,霍尔电压并没有正比增加,其横向电阻是量子化的,所以人们称这种现象为量子霍尔效应。如图2与图3,随着门电压的变化,霍尔电压出现了一系列的等值平台,设常量$k=\frac{h}{e^2}$,通过计算这些霍尔电压平台所对应的电阻,发现其阻值总在$\frac{k}{n}$附近,其中$n$为一个整数,且阻值与$\frac{k}{n}$的误差在$10^{-5}$以内。图3中的绿线代表纵向电阻,当横向电阻处于平台时,纵向电阻总是等于0。

1.3 整数量子霍尔效应的成因

  正常导体中载流子将互相碰撞实现整体上的定向移动从而产生电流,如果外加磁场,载流子受力偏离原先轨道,碰撞导体边界,在强磁场下的电子气中,由于磁场变强,电子圆周运动的半径减小,绝大多数电子无法直接达到导体边界反弹,它们将在磁场中持续做圆周运动,有一小部分边界电子,其运动轨迹将与边界碰撞,而不会进行持续的圆周运动,所以在系统稳定后,这些电子将在边界跳跃前行,发生弹道运输现象,如下图所示。

图4

  而由这种现象产生的电阻不与材料有关,只与电子本身相关,所以出现了$\frac{h}{ne^2}$的横向电阻。但是为什么$n$一定是整数呢?或者说为什么这些阻值一定是离散的呢?下面我们从能量的角度出发,解释量子霍尔效应的成因。

  电子在洛伦兹力的作用下进行圆周运动,其回旋轨道会发生量子化,这样的现象叫做朗道量子化,究其能量,存在简并的朗道能级,在每一个离散的朗道能级上,电子的能量为
$$
E_n=\hbar\omega_c(n+\frac{1}{2})
$$
  其中$\omega_c$为粒子的回旋频率,在高斯单位制下,根据
$$
\frac{mv^2}{r}=\frac{qBv}{c}, \omega=\frac{v}{r}
$$
  可得
$$
\omega_c=\frac{qB}{mc}
$$
  可见$\omega_c$与磁场强度有关。

  由电子能量的表达式可以看出,只有当平均内能远小于两能级的差值的时候($kT\ll\hbar\omega_c$),也就是说在低温强磁场下,朗道量子化才能被表现出来,当粒子为电子的时候,其每一能级容纳量常数$N$(为一整数)有如下范围

  其中$L_x$与$L_y$为系统的范围约束,当粒子为电子的时候,回旋速率为$\omega_c=\frac{qB}{mc}$,记磁通量量子为$\phi_0=\frac{h}{2e}$
$$
0\le N \lt \frac{meBL_xL_y}{hmc}=k\frac{BL_xL_y}{\frac{h}{2e}}=k\frac{\phi}{\phi_0}
$$
  其中$k$为一常数,其同时与粒子的自旋有关,对电子进行研究时,可见每一能级可容纳的电子数与磁通量成正比,也就是说当磁场很强的时候,每个能级能容下更多的电子,以至于几乎所有的电子都在较低能级上回旋运动,在这种状态下就出现了人们观察到的量子霍尔效应。

2 国际千克单位制

2.1 新千克单位制的定义

  2018年,第26届国际计量大会全票通过了新千克单位的定义,1千克被定义为“对应普朗克常量为$6.62607015\times10^{-34}J·s$时的质量单位”。

2.2 新千克单位制定义的原理

  由整数量子霍尔效应,有电阻
$$
R_k=\frac{\hbar}{e^2}
$$
  根据约瑟夫森效应可以得到导体中电压的关系式
$$
U(t)=\phi_0\frac{\partial\varphi}{\partial t}
$$
  更一般地,我们设
$$
U=\phi_0v
$$
  引入基布尔秤的使用,基布尔秤通过电流和电压的测量来精确测量对象的重量,其为安培称的更精确版本,其由力的平衡$mg=BIL$为原理,由于$U=BLv$,两式结合,得到
$$
\begin{align}
mgv&=UI=\frac{U^2}{R_k}\\
&=\frac{h^2}{4e^2}\nu^2\frac{e^2}{h}\\
&=\frac{h\nu^2}{4}
\end{align}
$$
  将$U$与$R$代入,即可得质量
$$
m=\frac{h\nu^2}{4gv}
$$
  其中$\nu$、$g$、$v$均为已定义的可测量变量,所以我们就可以由此将千克的定义与普朗克常量联系了起来。

参考文献

[1] Non-Abelian anyons and topological quantum computation, C Nayak, SH Simon, A Stern, M Freedman, SD Sarma, 2008
[2] Quantum Hall Effects Mark O. Goerbig, 2009
[3] New method for high-accuracy determination of the fine-structure constant based on Quantized Hall Resistance, K. V. Klitzing, G. Dorda, M. Pepper, 1980

电动滑板开发日志owo

材料准备

*每个原件说明后的括号内为其大致价格,价格低于1元的以1元表示

  1. 控制板

    Arduino Pro Mini:主控板,用于协调控制板上的各个器件,其作用有接受发送蓝牙遥控信号、转换电源电压、测量电池电压、测量霍尔元件电位、输出PWM控制信号到电调(20¥)

    HC05蓝牙模块:用于处理与遥控器的通信(20¥)

    霍尔元件及磁石:用于测量从动轮转速(15¥)

    按压开关:用于控制PWM信号的输出(1¥)

    电阻:用于将6节锂电池的24V电压分压供主控板测量(1¥)

    12*18PCB电路板:放置各个原件,虽然最后懒得往上面焊了(15¥)

  2. 遥控器

    Arduino Pro Mini:主控板,用于处理遥控器的相关信号,其作用有接受发送蓝牙控制信号、接受转换摇杆信号、转换电源电压、输出I2C信号到显示屏(20¥)

    HC05蓝牙模块:用于处理与控制板的通信(20¥)

    摇杆模块:用于控制速度与刹车(2¥)

    SSD1306显示屏:用于显示实时电量、速度、动力大小(20¥)

    8*2PCB电路板:放置各个原件(2¥)

    电源:南孚牌9V锌锰干电池(暗示广告费)(20¥)

  3. 其他主要元件

    长板滑板:本体(110¥)

    滑板海鸥架:用于控制滑板转向(2 * 60¥)

    锂电池:用于给电机提供动力,这里选用了5200mAh的6C锂电池(285¥)

    9052轮:轮越大越稳,就买了大的(4 * 40¥)

    N5065 330KV 电机:控制轮子转向等(180¥)

    120A电调:用于控制输出到电机的电流(220¥ )

    电机支架、主动轮、皮带等:用于固定、连接各个部件(50¥)

  4. 配件

    B6充电器:真正意义上的万能充,用来平衡锂电池充电(110¥)

    CP2102 TTL转USB转接头:用于给arduino烧录程序,需要手动reset,不太好用,换用了下面这款(5¥)

    FIDI下载器:用于给arduino烧录程序(40¥)

    各种杜邦线:连接用,买长了,其实5cm就够用了(20¥)

    电调编程器:设置电调参数(35¥)

    3M胶带:绝缘与固定(5¥)

    热缩管:绝缘与固定(5¥)

    LED灯:显示状态与照明(5¥)

    树莓派3B+:本来打算做一个树莓派project,但是感觉有些水,就把树莓派用来烧录arduino BootLoader了

    电烙铁、热熔胶枪、万用表、钳子、螺丝刀等:杂七杂八的常用工具不细说了

总花费大约1k出头,考虑到同等的速度、功能、续航、可扩展性等因素,比市面上的电动滑板还是要便宜很多的,下面就正式开始介绍开发过程啦

一、动力系统

  由于很多元件没有统一标准,需要自行设计很多用来连接的部件,过于麻烦,所以这一部分是找店家整体定做的,如果自行做图纸设计找厂家出模具的话很多部分甚至能便宜一半的价格,但是这毕竟是程序设计课… 不需要那么多过于硬核的设计工作…

  使用了N5065的无刷电机,50代表其外径,65代表其长度,这里使用了330KV的版本,也就意味着电压每升高1V,转速就会升高330。同时电机的输入电流是三相的,避免了电刷的使用,减小了摩擦力,提高了工作效率,但这也就意味着需要使用电调来控制了。电调的全程是电子调速器,它可以把电池输出的直流电在三根电线中循环输出,控制电机转动,并且可以随时改变他们的电流值,以达到控制速度的效果,电调为主控板提供了BEC接口,主控板㛑可以通过这个接口给电调和电机输出PWM信号。

120A电调

N5065电机

  PWM信号的全称是脉冲宽度调制(Pulse-width modulation),不同于普通的数字信号和模拟信号,PWM是一段波动的数字信号,它只有1和0两种状态,但是它可以用一段时间内1的时间占比来表示实际数值,比如在一段20ms的时间内,想要表示0.5即可让1输出10ms,在实际使用的时候我们需要把遥控器传来的力度大小转换为PWM信号输出给电调,代码如下:

1
2
3
4
5
#define MOTOR_BASE 1100
#define MOTOR_COEF 8

PWMTime =
(double)MOTOR_BASE + (double)power * (double)MOTOR_COEF;

  然后使其在周期内使用Arduino自带的随动系统Servo进行输出即可

1
2
3
4
5
6
7
8
#define MOTOR_PIN 12

motorServo.attach(MOTOR_PIN);

if (nowTime - lastContral >= CONTRAL_INTE) {
lastContral = nowTime;
motorServo.writeMicroseconds(PWMTime);
}

二、通信系统

遥控器上的蓝牙模块

主控板上的蓝牙模块

  由于WLAN价格昂贵,ZigBee资料太少,这里最终选择了蓝牙模块进行通信,首先我们需要通过串口转USB的转换器连接蓝牙,进行蓝牙相关信息的设置,可以参考以下步骤:

  1. EN置高,通电进入AT模式
  2. 通过串口38400波特率,8位数据位,1位停止位进行命令传输
  3. 通过串口发送AT\n进行连接测试,返回OK即连接成功(注意一定要加上换行符)
  4. 主从机发送AT+NAME=<Param>\n给它们设置一个可爱的名字/w\
  5. 主机发送AT+ROLE=1\n,从机发送AT+ROLE=0\n设置主从模式
  6. 主从机发送AT+PSWD=<Param>\n设置配对码,方便调试
  7. 从机发送AT+ADDR?\n查询mac地址,记为MACA
  8. 主机发送AT+BIND=<MACA>\n来绑定从机,这里的MACA为上一步返回值
  9. 主从机默认波特率应该为9600,这意味着1s内只能传输9600bit即1200Byte的数据,1ms大约只有1Byte的数据传输,这个速度不太行,所以主从机发送AT+UART=38400,0,0\n来重置波特率

  至此,蓝牙连接完毕,将蓝牙连接到arduino只需要将串口连接,即进行RX-TX, TX-RX的连接即可,在程序内采用如下方式打开串口并进行数据的接收与发送

1
2
3
4
5
6
7
Serial.begin(38400);

if (Serial.available() > 0) {
char tmp = Serial.read();
}

Serial.print("test");

  由于蓝牙模块的双工异步处理,可能会导致发送端与接收端统一频率下接发数据产生延迟而造成数据丢失或者累积错位,这也就导致了遥控器发出信号主控板无法抄收、主控板抄收延迟等问题,所以我们必须规定一定的数据格式,或者高端一些叫做通信协议,我在进行设计时主要遵循以下两个原则:

  1. 定义B字符为起始符,E字符为结束符,只以起始符作为读取的开始,一旦遇到结束符立马停止读取

  2. 定义数据包大小为定值,即在B和E中间的数据必须为指定长度的字符,否则数据包作废

  这样设计通过增加数据的复杂性来提升数据的安全性,但是仍会遇见一些问题,初步分析是由于在设计之初没有考虑到蓝牙的连接时长,即默认蓝牙一旦开机就会连接,并且开机后双方均以相同的频率收发数据,如每100ms发送一次,每100ms接受一次,这就很容易导致发送端的数据在接收端缓冲区积累,造成数据延迟,对于这个问题有两个解决办法,一是每次只取缓冲区内最新数据读取,丢弃冗余数据,二是消除接收端频率设计,使接收端保持接收,即发送端每100ms发送一次,接收端每时每刻都在接收并且随时覆盖旧的数据,每100ms进行一次更新,这两种解决办法本质相同,二更安全也更容易实现一些,最终蓝牙传输代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* ---- 发送端 ---- */
Serial.print("B");
Serial.print(power); // if power < 10 then power = 10 保证数据长度
Serial.print("E");

/* ---- 接收端 ---- */
int lossCount;
if (Serial.available() >= 4) {
char tmp = Serial.read();
delayMicroseconds(FLUSH_INTE);
String powerString;

if (tmp == 'B') {
while (Serial.available() > 0) {
tmp = Serial.read();
if (not ISNUM(tmp)) {
if (tmp != 'E') powerString = "";
break;
}
powerString += tmp;
delayMicroseconds(FLUSH_INTE);
}

if (powerString.length() > 0) {
int power = getNumber(powerString);
if (power > 100 or power <= 0) power = 50;
PWMTime =
(double)MOTOR_BASE + (double)power * (double)MOTOR_COEF;
} else {
lossCount++;
if (lossCount >= 5) {
PWMTime = 1500;
lossCount = 0;
}
}
}
}

三、控制系统

  采用摇杆进行控制,前推是前进,不同的位置对应着不同的速度,后拉是紧急刹车,中位是滑行摇杆

  摇杆有VCC、GND、VRx、VRy、SW五个接口,由于我们只需要一个方向的输出,所以只需要接VCC、GND、VRx即可,其中VRx可以连接任意一个模拟输入口,并通过以下方式读取输入

1
2
3
#define REMOTE_PIN A0

int sensorV = analogRead(REMOTE_PIN);

  这样读入的是一个int类型的变量,其值为0到1023,通过以下代码可以转换为0V到5V的电压输出

1
2
double V = (double)sensorV * 5.0 / 1023.0;
int power = (double)sensorV * 100.0 / 1023.0 + 0.5; // 或直接这样转换为一个0到100的动力大小输出

四、测速系统

霍尔元件及其磁石

  测速采用霍尔元件,根据霍尔原理,当其感应器附近有磁场通过时其原件内部会产生一定大小的电势差,装在从动轮上的磁石每经过海鸥架上霍尔元件一次,霍尔元件就会将这样的电势差通过AD转换器转换为数字信号,传给主控板,主控板通过如下方式进行速度的测量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define HOLL_PIN 10
#define WHEEL_DIAM 9.0

/* setup */
pinMode(HOLL_PIN, INPUT);

/* main loop */
int hollSta = digitalRead(HOLL_PIN);
if (hollSta != lastHollSta) {
hollCount++;
lastHollSta = hollSta;
}

/* upload loop */
unsigned long rps = hollCount * UPLOAD_FREQ / 2;
hollCount = 0;
unsigned long velocity = PI * WHEEL_DIAM * (double)rps / 100.0;

Serial.print("BV"); // speed
sprintf(output, "%03d", velocity);
Serial.print(output);
Serial.print("E");

五、电源系统

电压传感模块

  遥控器使用干电池连接RAW口供电,控制板使用锂电池的BEC接口供电,干电池是一次性的,耗尽后换新即可,而锂电池一旦过耗,可能会减少电池寿命,所以需要实时监测锂电池电压,在截止电压时及时切断电路,防止锂电池过耗。这里采用了R1(3kΩ)与R2(750Ω)的电阻串联来达到对24v+电压的分压作用,分压后便可直接将R2两段电压输出到主控器的模拟输入口经过计算即可获得电路实际电压,这里的分压比是1:4,但是考虑到锂电池满电电压是4.2V,即6C锂电池最大可能达到25V+,为了防止烧坏电路,我又在R2上并联了3kΩ的电阻R3,则R2、R3的阻值为$\frac{1}{\frac{1}{3000} + \frac{1}{750}}=600(Ω)$,分压比变为了1:5,就可以放心使用啦,程序里面只需要读取模拟输入口数据即可测量电压

5200mAh 6C锂电池

1
2
3
4
5
6
7
#define BAT_PIN A0

int batSensorV = analogRead(BAT_PIN);
double batV = batSensorV * (5.0 / 1023.0);
aveBatV = (double)batCount / (double)(batCount + 1) * aveBatV +
batV / (double)(batCount + 1); // 这里为了防止溢出牺牲了一部分精度
batCount++;

  但是!锂电池的电压并不与电量成正比,经过单位时间相同功率的电能耗费实验,大概可以得出锂电池电压与电源的对应关系,于是就可以通过如下函数预估当前的大概电量啦ovo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int voltageToPersent(double v) {
if (v >= 4.17) {
return 100;
} else if (v >= 3.8) {
return 60 + (int)((v - 3.8) / 0.4 * 40.0 + 0.5);
} else if (v >= 3.1) {
int res = ((v - 3.1) / 1.1 * 100 + 0.5) - 4;
return res > 0 ? res : 0;
} else {
return 0;
}
}

Serial.print("BP"); // power
sprintf(output, "%03d", voltageToPersent(aveBatV));
Serial.print(output);
Serial.print("E");

六、时序控制

  基本沿袭了arduino本身的中断控制等程序,除此之外还调用了micros()函数获取自程序运行到现在的时间戳进行时间的计算,这里需要注意由于arduino开发板的问题,在一些系统内int类型是16位而非32位的,需要使用unsigned long来存储时间变量。

  我采用了如下方式进行时间控制

1
2
unsigned long nowTime = micros();
if (nowTime - lastContral >= CONTRAL_INTE)

  我们定义上传时间间隔为100ms(100000μs),PWM信号时间间隔为2ms(2000μs),串口缓冲区刷新即数据传输间隔为0.3ms(300μs),绘图时间间隔为1000ms(1000000μs)

1
2
3
4
5
6
#define CONTRAL_INTE 2000
#define FLUSH_INTE 300
#define UPLOAD_INTE 100000
#define UPLOAD_FREQ 10
#define DELTA 500
#define DRAW_INTE 1000000

七、安全系统

  由于该产品面向实际使用,所以需要格外注意安全问题,比如蓝牙模块失联时不能继续发送信号、蓝牙发送数据错乱时跳过数据、主控程序失控时有外部方式中断电机等,其中有一部分安全问题由电调解决,如电调设定温控模块、电流保护模块等,其他问题在代码层面主要采用了如下解决方案:

  1. 蓝牙失联或数据无法正常接受数据超过5次即进入滑行模式(PWM时间1.5ms为滑行模式)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (powerString.length() > 0) {
    int power = getNumber(powerString);
    if (power > 100 or power <= 0) power = 50;
    PWMTime =
    (double)MOTOR_BASE + (double)power * (double)MOTOR_COEF;
    } else {
    lossCount++;
    if (lossCount >= 5) {
    PWMTime = 1500;
    lossCount = 0;
    }
    }
  2. 蓝牙失联屏幕进行提示并终止循环程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #define BLUETOOTH_STATE 10

    if (digitalRead(BLUETOOTH_STATE)) {
    noLink = false;
    noLinkDraw = false;
    } else {
    noLink = true;
    }

    if (noLink) {
    if (not noLinkDraw) {
    drawNoLink();
    noLinkDraw = true;
    }
    return;
    }
  3. 主控板状态异常(如测速异常、电压传感异常等)进行屏幕提示

    1
    2
    3
    if (uploadCount == 0 or speedCount == 0 or batteryCount == 0) {
    drawError();
    }
  4. 外部按钮控制:

  在主控板到电调的PWM信号上加入了一个按压式开关,放在了正面前脚掌处,只有在脚踩时PWM信号才会正常传输

一个不怎么好用的按钮

八、显示系统

  使用了SSD1306模块0.96寸的OLED屏幕作为显示屏,将arduino A5与A4两个口置为SDA和SCK输入口,通过I2C方式与显示屏进行数据传输,代码实现上直接调用了u8g2库,使用了其HWI2C接口,主要实现了速度、动力、电量显示与异常提醒等函数,考虑到arduino的处理性能和绘图能力嘛emmmm… 直接每秒一帧了… 后期考虑加入另外一块arduino作为绘图卡专门用来处理显示数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(
U8G2_R0, U8X8_PIN_NONE, A5,
A4);

u8g2.begin();

void drawError() {
u8g2.setFont(u8g2_font_helvB18_tr);
u8g2.clearBuffer();
u8g2.drawStr(8, 64, "ERROR!");
u8g2.sendBuffer();
}

void drawData(int capacity, int velocity, int power) {
u8g2.setFont(u8g2_font_helvB10_tr);
u8g2.clearBuffer();
char output[32];

sprintf(output, "BTR:");
u8g2.drawStr(8, 16, output);

sprintf(output, "SPD:");
u8g2.drawStr(8, 48, output);

sprintf(output, "PWR");
u8g2.drawStr(70, 16, output);

u8g2.setFont(u8g2_font_helvR10_tr);

sprintf(output, "%d%%", capacity);
u8g2.drawStr(8, 32, output);

sprintf(output, "%dm/s", velocity);
u8g2.drawStr(8, 64, output);

if (power >= 88) {
u8g2.drawBox(65, 20, 45, 10);
} else {
u8g2.drawFrame(65, 20, 45, 10);
}

if (power >= 75) {
u8g2.drawBox(65, 30, 45, 10);
} else {
u8g2.drawFrame(65, 30, 45, 10);
}

if (power >= 63) {
u8g2.drawBox(65, 40, 45, 10);
} else {
u8g2.drawFrame(65, 40, 45, 10);
}

if (power > 50) {
u8g2.drawBox(65, 50, 45, 10);
} else {
u8g2.drawFrame(65, 50, 45, 10);
}

u8g2.sendBuffer();
}

void drawNoLink() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_helvB18_tr);
u8g2.drawStr(8, 64, "NOLINK!");
u8g2.sendBuffer();
}

九、成品展示

  这里放不了GIF和视频,视频就直接加在附件了,电路图是手绘的,比较简单,在这里就不给出了

遥控器正面

遥控器侧面

主控板

十、其他

  这里主要用来放一些收获不足和想吐槽的话…

  1. 这次project收获挺多的,算是对电路原理有了初步的了解,也产生了浓厚的兴趣,下一步想做一个微型无人机集群,但是考虑到成本问题嘛.. (室内定位用的UWB模块一个50左右,基站2000左右,实在是有些昂贵)可能要咕一咕了
  2. 最主要的收获主要还是硬件层面的一些接口、协议,比如蓝牙如何设置,串口如何使用、GPIO引脚的作用、数字信号模拟信号PWM信号的发送方式、各种模块的工作原理及其作用等等等等,其次就是代码上对于单线程的单片机,使用中断或集群等操作来达到多线程的目的,以及各种各样的时间处理方式、优美减少BUG的编码方式之类的
  3. 不足的地方大概是布线太硬核(丑)了,以及最开始买东西的时候没有和商家充分沟通,买来了一些不能用的东西,还有就是一些底层设计上优化不到位(为了遥控器的显示几乎占用了arduino pro mini的所有动态内存,这是相当危险的!)
  4. 其实本来是想做一个树莓派人脸识别门禁的project,但是大家都在人脸识别,恰好看到生命取向的同学用滑板非常酷,于是在已经完成了那个门禁系统的前提下,中途易辙做了电动滑板,而门禁用的磁吸磁石… 就被我用来当霍尔元件的磁石了哈哈哈

  5. 这个CP2102是分6pin和4pin的,我买的是4pin的版本… 他自己不带DTR模块导致需要手动按arduino的reset键,关键是这个时机很难把握,然后在youtube上看到了毛子的神奇解决方式… 大概是把芯片组第三号引脚用细铜丝引到RST针脚上… 技不如人,告辞

毛子牛逼!

特别鸣谢:感谢万能的老爹用他强大的知识储备耐心帮忙解答了各种各样的问题,以及感谢辰辰小朋友全程陪同及参与测试2333