四轴飞行器攻略3:串口通信

虽然四轴飞行器还没有最后完工,但我觉得这不妨碍先发一部分攻略。作为一个新手,我在调试过程中遇到了很多问题,现在不记下来的话,等完工了很可能都忘了;另外,高手们也可以通过我的攻略,看看有没有什么错误,或者哪里可以优化;最主要的是,独乐乐不如众乐乐,分享是DIYer们的精神之一 :)

上次整理了四轴攻略的目录,今天正式把它放在导航栏成为一个新项目,目录的顺序是按照我制作的顺序来组织的。
比如刚买回来电机和电调,本来应该先做电机控制实验。但是看了说明书发现这种电机还需要初始化,而且初始化需要可调比例的PWM方波(具体的设置方法会在后续的电调部分介绍)。对于控制来说,调节方法至少有两种:一种用旋转电位器连接Arduino的模拟输入,另一种是用计算机或者手机通过串口控制调节。

因为电机的初始化只需要做一次,为了这个增加电位器好像没有必要;而串口通信则会贯通在整个控制和调试的过程中,所以我决定先从它开始实验。

串口通信其实之前已经做过很多小实验了,简单的一段演示代码如下:

Serial.begin(9600);  // 设置波特率
Serial.write(123);   // 向控制端写数据
while(Serial.available()) {
       // 如果有数据,则读取数据
       byte data = Serial.read();
}

很快你会发现这段简单的小代码有一些问题。

1. 传输速度:
代码中的9600是波特率,也就是数据通信的速度,它是目前比较流行的传输速率。以这个速度通信的话,每发送一个字节(Byte)到控制端需要的时间大概是1毫秒。需要注意的是,为了精确控制四轴的平衡,我们需要尽量在短时间内多读取各种传感器的值。以目前的350Hz的采样率来说,每2.85毫秒就需要读取一次陀螺仪和重力感应器。这种情况下,1Byte/ms的传输速度显然是不能容忍的。解决的办法就是修改波特率,Arduino支持的波特率包括:300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 和 115200。如果修改的话,相应的控制端也需要修改成一样的。
大家可能会说,为什么不全都用高速的呢?实际上能使用多少的波特率,跟处理器的主频有关;而且主频最好是波特率的整数倍,否则的话可能会增加错误率。对于我这个四轴来说,如果用USB和电脑通信,可以达到最高的115200;如果用蓝牙和手机通信,只能达到9600的波特率(因为蓝牙模块修改波特率还需要额外购买一个控制板)
很让人高兴的一点是,Arduino支持在运行过程中动态修改波特率。所以可以首先使用9600连接,当发现连接对象是电脑时,调整为115200:

    if(controlType == 100 && !connectToPc) {
      Serial.end();
      connectToPc = true;
      Serial.begin(115200);
    }

需要说明的是,只有发送数据才有这个问题,对于单向接收数据基本是没有关系的。因为Arduino接收的数据会放在缓冲区里,在读取的时候数据已经下载完毕,主程序不需要等待接收。

2. 关于通信协议
对于一个新手来说,协议可能是个特别吓人的词汇,感觉是权威机构才能制定的东西。事实上,在单片机领域经常需要自己定义协议。下面我们看看到底是怎么回事。
前面的代码中演示了如何接收一个字节的数据,问题是:如果需要接受多个控制数据,读取程序怎么知道哪个字节是控制那个参数呢?
我用了一个非常简单的协议,每组数据6个字节,其中第一个字节是FF,其他字节都小于255。这样一来,我们在读取数据时,如果看到FF就知道它是数组的第一个元素了。
当然我这里是一个简单的协议,对于复杂的系统,数据中很可能也带有FF数据,这种情况该怎么办呢?其实也很简单,就像高等语言中的转义功能。如果数据中有FF,那么我们把它改成FE01。等等,如果数据中原来就有FE01怎么办?我们可以把所有的FE改成FE02。这样就完成了转义。学过HTML语言的同学,可以用&转义符做参考进行理解。

3. 校验位
接下来是另外一个问题。单片机通信并不像我曾经想象的那样稳定和精确,它其实是有可能丢数据的。例如串口数据到达的时候,Arduino的中断正好被触发,这时候数据就丢了。
丢一个数据可能会带来非常严重的后果,例如我的控制参数,第一个是油门,第二个是PID的积分参数,第三个是PID的微分参数。如果第二个数据丢了,我就会认为第三个是积分参数,这两个参数如果相差较大的话,也许四轴就坠毁了……
所以在协议里,还需要确认一下收到的数据是否完整和正确,这里就需要用到校验位。一个常用的简单的方法是,把一组数据依次进行异或操作,把结果作为最后一个字节的数据一起发出去。接收端收到数据后,也相应的做一次异或操作,看看两个值是否相等。从数学上看,也可以把接收数据全部异或起来,如果等于0就对了。(这种异或校验法不能完全保证正确性,有一定的概率会误判,但是大多数情况够用了)
如果校验错误的话,我们宁可把这一组数据丢掉,也不能使用错误的参数。

void loop()
{
    while(Serial.available()) {
        pushData(Serial.read());
    }
}
int bufferIndex = 0;
void pushData(byte data)
{
  //验证是否数据的开始
  if (bufferIndex < 1 && data != (byte)0xFF) {
    bufferIndex = 0;
  } else {
    buffer[bufferIndex] = data;
    if (bufferIndex == 5) {
      setParams();
    }
    bufferIndex = (bufferIndex + 1) % 6;
  }
}
void setParams()
{
  // 因为超声波中断可能导致数据丢失,所以需要额外的一个字节用于校验
  if(buffer[1] ^ buffer[2] ^ buffer[3] ^ buffer[4] == buffer[5]) {
    ctrlPower = buffer[1];
    reportType = buffer[2];
    P_Control = buffer[3] / 200.0;
    I_Control = buffer[4] / 200.0;
    // 其他处理
  }
}

总结,其实所谓的“协议”,就是在数据通信时商量好的“规则”,按照这个规则,我们才能保证接收到正确的数据。



对 “四轴飞行器攻略3:串口通信” 的 42 条 评论

  1. 老薛 说:

    非常有参考价值!

    • 向大家推荐这位物理学专业人士老薛,点他的名字就可以去博客啦 :)

      • 老薛 说:

        都是熟人,哈哈!我在糖伯虎的乐高网上发过一个用乐高研究音叉声波干涉现象的帖子,大家笑我太疯癫。:)

        • 糖伯虎 说:

          你笑他人看不穿,哈哈。主要是您的层次和我们不是一个级别的,咱们看不懂啊。

          • 别谦虚啦,呵呵,我经常在中文乐高潜水,向糖伯虎同学偷学了不少武艺 :D
            话说家里的乐高沉睡很久了,等这个四轴告一段落,得拿出来活动活动

  2. 糖伯虎 说:

    pid的参数为什么要用手机发送?avr不能处理吗?我的想法是手机只发送平衡点就行,通过调整新的平衡点,改变飞机的悬停,俯仰等状态,让飞机自己飞去吧。说得不对请拍砖

    • 你说的没错,pid参数选好以后,就可以写在avr里面了。
      以前我调试磁悬浮的时候,pid参数试了很多很多次才调到一组合适的值,每改一次参数就要改程序重新烧入arduino一次,非常麻烦。
      调四轴的话,如果在代码里面试参数会更麻烦,因为需要到户外测试。
      伯虎兄对此有什么好建议吗?比如不需要大量尝试,有没有可能用在程序里自动计算收敛参数?

      • 老薛 说:

        江湖传闻有一种自适应的PID算法,能够自动调节PID参数。我也只是听说,没有亲见过。

      • 糖伯虎 说:

        我一直觉得调PID参数就是一个体力活,我也没好的办法,老薛的江湖传闻我也听过,希望老男孩弄出来,我现在几乎是每天来看你的blog,加油啊!

        • 多谢支持!这个春节相当挫败啊,打碎了六七个叶片,除了高度控制起了一点作用外,基本没有什么进展。
          好在网上有这么多朋友支持,不然真是坚持不下去了 :)
          最近列了一些可能的原因,再慢慢修正。有朋友说最主要的问题是减震,如果振动不消除的话,再怎么调PID也没用。
          另外,温漂和校准也是要考虑的,而这个之前我认为是可以忽略的(我以为PID可以弥补误差)

          • 老薛 说:

            动力哥的采样有用卡曼滤波之类的吗?我觉得振动,温漂这诸多因素都能让PID变得非常不稳定。但是如果有了一个好的滤波,就能把这些不重要或者“虚假”的信号去掉,让PID安定下来。

            1960年代登陆月球的阿波罗号导航处理器只能存储几K的程序和数据,但是就是用了卡曼滤波。那些飞行器的振动,温漂及各种误差想必非常剧烈。

          • 好多人也提到了用卡尔曼滤波,不过我没有找到详细的资料,
            后来有朋友说用平均滤波就可以了,类似于X=0.9 * X + 0.1 * newX这样的,就没有再深究。
            听你这么一说,我觉得有必要研究一下卡尔曼滤波,老薛手头有相关的资料吗?

      • 小小桃 说:

        不知道动力哥有没有用过 Matlab/Simulink 这个软?
        可以在里面建好飞行器的模型和controller的模型,再调整PID的参数,这样会方便很多 ;)不用每次调整参数都烧录一次程序,还能减轻摔机。
        不知道行不行得通。

      • 小小桃 说:

        自适应PID 可以根据被控物体的状态量不断自动调节控制参数,可以试试极点配置,自适应PID控制的其中一种方法,计算要求少一点。

  3. zEus 说:

    看你原来图片的蓝牙模块,应该是可以直接改波特率的吧,直接用arduino接上蓝牙模块然后通过tx发送修改指令就行的吧,我记得命令好像是AT开头的。

    • 原来如此,多谢!
      我买这个蓝牙模块的时候,那个卖家跟我说改波特率的话需要另外买一个板子。
      其实另外一个板子也就是通过tx发送指令吧

    • SSS_SXS 说:

      这位兄弟,是不是把串口通讯和GSM模块搞起来了,AT指令是对GSM模块的速率调整,和蓝牙模块没有关系,说得不对请指教!

  4. zEus 说:

    对于串口丢数据这点受教了,呵呵,我串口蓝牙还没有丢过数据,所以也就没做处理。
    不过那个pid参数需要每次都从手机发送到arduino吗,改变的时候发送一次不行吗?我程序还没写完,现在用的三个字节的数据,第一位来表示命令类型,比如,改变油门,或者改变姿态等等;二三位是数据的高位和低位。这样将来需要修改各个参数的时候加一种命令就行了。

    • 对,只是改变的时候发一次 :)
      因为超声波测距用到了中断,在Arduino的官网上提到了,在中断处理的过程中如果有串口数据到达,可能会丢失。其实事实上有没有丢掉,我也不太清楚

  5. 老薛 说:

    平均滤波在复杂和剧烈变化的环境下就不怎么好用了。卡曼滤波是根据统计规律计算出来的“最优化”滤波。我对它的了解来自下面这个非常好的网站:
    http://www.convict.lu/Jeunes/RoboticsIntro.htm
    目前我还仅限于理论了解,还没有实践过。动力哥可以试试看。

  6. alajl 说:

    ardunio怎么玩摄像头,这个方面,一直都想整个玩玩

    • Arduino的速度,处理视频可能有点困难。
      常见的一种方案是添加一个wifi路由器,这个路由器自带处理器和Linux系统。
      通过这个路由器就可以由网络来发送指令和查看视频了,路由器和arduino之间还是通过串口通信

  7. boboking 说:

    http://item.taobao.com/item.htm?spm=1103*J9a.3-8BYf7.h-3Q9bXR&id=12549817091&

    兄弟看看能否采用这个做主控芯片,处理器stm32f103rbt6 arm cortex-M3 32位处理器 工作频率: 72MHz

  8. boboking 说:

    视频还是采用wifi摄像头比较好,与控制系统分开,也比较简单。

  9. zEus 说:

    呵呵,我没搞过GSM模块,我用的蓝牙模块跟动力哥用的属于同一种,AT是修改波特率,连接密码等参数的指令。比如通过串口发送“AT+BAUD4”是将波特率设置为9600。我想指令格式应该和模块有关,所以,可能您说的GSM模块跟这个蓝牙模块的指令格式一样,甚至我猜,这会不会是串口模块的一种标准呢,呵呵。

    • zEus 说:

      这个是回这位 SSS_SXS 兄弟的。

      • SSS_SXS 说:

        我最近搞的GSM模块,涉及太多AT指令,一上来看到你写AT,自然就想到GSM模块的AT指令集。至于蓝牙模块是用“AT+BAUD4”改变波特率,真是跟GSM模块很相似,哎,脑子里有多了好多疑问。

  10. SSS_SXS 说:

    动力兄,是不是完全基于平均滤波来开发四轴飞行器的?

    MS卡曼滤波应用很广泛且很实用,是不是应该从卡曼滤波开始着手重新审视四轴

    飞行器的开发?至于卡曼滤波我粗略读了几篇文章,一头雾水,实在是看不太懂

    • 前几天看了卡尔曼滤波,从我的理解看,卡尔曼和平均滤波的方式主要差别在于平均的方式。
      平均滤波一般都是线性参数加权平均,而卡尔曼则是需要先估计测量值和预测值各自的误差范围,然后用均方的形式进行加强平均。
      我试了在没启动电机的情况下,用手拿着四轴摆动,这两种滤波方式几乎没有差别。

      我猜想电机启动后,加速度传感器的读数就会剧烈跳动,这时候可能差别就显示出来,最近我会测试一下,有结果了跟大家分享。

    • 另外,卡尔曼滤波的计算量会稍大一点,我那个350Hz的采样周期本来也只是勉强够用,如果改用卡尔曼的话,只能降到300Hz了。

      我在调试过程中有个很大的问题,就是没有在电机启动的情况下记录过测试数据,都是关了电机用手摇晃的,这和真实情况差别很大。
      主要是担心有危险,下一步可能需要考虑把数据发到手机记录,这样才好分析

  11. peter cao 说:

    I have a reciever which can control a 4 channel double rollers helicopter (such as “nine eagle” but the two motors changed to brushless motors to fly and also accept coding (c language) can this reciever change code to control this kind of flying machine?

    • A 4 channel receiver is enough for a four-axis plane. I mean it is enough for the remote controlling.
      But I am afraid you have to make other hardware, as well as changing codes.
      The working principles between helicopter and four-axis plane are totally different.

  12. 王俊 说:

    void loop()
    {
    while(Serial.available()) {
    pushData(Serial.read());
    }
    }
    软件我懂一点,硬件不懂。冒昧问一句。
    这个是不是只能监听一个传感器了?
    就像socket一样?auduino中可以多线程么

发表评论

可以使用下列 XHTML 标签:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>