Arduino解析FUTABA PPM信号-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 5368|回复: 5

Arduino解析FUTABA PPM信号

[复制链接]
发表于 2021-6-30 10:03 | 显示全部楼层 |阅读模式
本帖最后由 gangguo 于 2021-6-30 15:40 编辑

PPM信号是航模遥控器输出的一种标准信号,从PPM信号中可以获取7-9个通道的遥控指令数据。PPM看起来很像PWM,很多模型爱好者对于它们产生了误解,有些朋友认为PPM和PWM就是一回事,其实不然,下面我们先说明一下它们之间的区别和关系。
PWM,是英文Pulse Width Modulation的缩写,意思就是脉冲宽度调制。脉冲就是由高、低电平组成的信号序列,其中高电平的时间就是这里所说的脉冲宽度,也就是高电平维持的时间,单位为微秒,范围在500-2500微秒(us)内,或0.5毫秒-2.5毫秒(ms)。如下图所示:
脉宽与舵机.jpg
这样的一个脉冲通常用来控制一个通道,即一个舵机,如要控制多个舵机,则需要多个这样的通道,例如:我们要控制一架固定翼模型飞机,则我们至少需要四个通道来分别控制油门、副翼、升降舵、方向舵,各个通道的脉冲宽度控制各个舵机转动。
PPM与PWM对比.gif
图中第一个波形为PPM信号,第二个波形为一通道的PWM,它对应到PPM信号的“K1”,第三个波形为二通道的PWM,它对应到PPM信号的“K2”,依次类推,“K8”对应到第八通道的PWM。K1的前面及K8后面还有一个比较“宽”的脉冲,它的宽度大于所有通道的脉冲宽度,这个也称为“同步脉冲”,在这样的一帧信号中,找出信号的“头”很关键,就如同在SBUS信号解析的过程中,要找到数据的开头,才能正确的解算出各通道的数据。PPM信号“同步脉冲”就可以作为“帧头”来使用,只要判断一个脉冲大于通道的“正常值”,那么接下来的一个脉冲就是1通道的数据。这里要注意的是:PPM中的通道脉宽比实际的PWM脉宽要“窄一点”,这里是由于在PPM信号中需要接入脉冲间隔,以区分通道,而PPM信号帧的总长又不宜过长,因此把每个通道的脉宽“砍掉”一个同样的宽度作为间隔,我们在计算通道PWM脉宽时还应该把这个被砍掉的部分加上。
下面开始介绍我的解算思路。很显然,PPM信号不能像SBUS解算那样使用串口,因为PPM就没有“波特率”,它的实质就是一序列串在一起的脉冲,要解算它实质就是要把这些脉冲一个一个地采集进来。在Arduino中有一个专门用于采集脉冲宽度的函数:pulseIn(),这个函数可以用来完成PPM解析,但是用这个函数有一些弊端:1、它会“死等”脉冲的到来,也就是脉冲不来,它就会在那里永远等待;2、当在执行一个脉冲的采集时,程序依然会停在那里等待采集完毕,这样的话,整个解算过程即要等待8个通道及一个同步脉冲的总时间,加在一起是20毫秒,如果这个解算过程只是用于演示,那么我们可以接受,但如果是用于实时控制,比如四轴飞行器,这么长的采集周期势必会让整个控制崩溃,因此,我们必须寻找其他的解算方法。(这里举个例子,玩过APM飞控的朋友应该知道,APM的遥控信号输入是使用的是PWM通道独立输入,而采集这些信号的任务都不是有主控芯片mega2560来完成的,完成这个任务是由协处理芯片:mega32,它同时也是USB转TTL芯片,这说明采集多路PWM确实是一个比较“繁琐”的过程)。那么采用什么办法来做呢?
单片机系统都有外部中断,可以用中断来处理这些“粘在一起”的脉冲。这次试验用的Arduino板为:NANO板,这个板(MEGA328)使用Arduino官方库时有两个外部中断:D2及D3口,试验中使用了D2,即外部中断0,触发方式设置为“跳变”,即脉冲的上升沿及下降沿均触发中断,并在中断处理函数中判断触发方式(上升沿或下降沿),然后分别记录进入中断的时刻(使用Arduino的时间函数:micros()),然后下降沿时刻减去上升沿时刻,即可得到一个脉冲的宽度(这个方法同样可以用于单通道PWM的采集,或者超声波测距)。
那么如何处理这一串脉冲呢?如何正确获得通道PWM呢?我的方法是:连续采集20个脉冲宽度放在一个数组中,然后去数组中寻找“同步脉冲”,找到它之后,紧随其后的8个数组元素就是我们需要的通道PWM数据。为什么一定要采集20个呢?因为我们无法确定第一次采集到脉冲是哪个通道的,除非“运气好”,一开始就获取到了“同步脉冲”,因此,如果我们非常“小气”地只采集9个,几乎不可能容易地从中找到正确完整的数据,当然也可以做一个比较细致、复杂的解算程序来完成(如果你是一个拼图高手),而每次采集20个,则保证了每一次采集到的脉冲序列中至少包含一帧完整的数据,这样就可以简化解算的过程,只需要找到“同步脉冲”,然后从它之后顺序取8个脉冲,其余的数据丢弃,然后进入下一轮采集。

本次试验中使用的遥控器仍为FUTABA T10CHG,试验中将发射模式设置为“2.4G 7CH”,就是飞模拟器使用的模式,然后用一个音频接头改装连线,连接遥控器背面的PPM信号线及地线。PPM信号线连接到NANO板的D2口,地线就在NANO上找一个GND接上。
PPM1.jpg

PPM2.jpg
  1. #include "Timer.h"
  2. Timer t;
  3. int ppm_flag=0;//为0时表示ppm帧未采集完成,每次低电平出发中断完成一个PPM_DATE的采集,此时标志位加1,当完成一帧采集后,标志位清0.
  4. char flag_in=0,flag_out=0;
  5. int ppm_i=0;
  6. long int h_now,l_now;
  7. int ppm_date[20];
  8. int channels[8];

  9. void setup() {
  10.   Serial.begin(115200);
  11.   pinMode(2,INPUT);
  12.   attachInterrupt(0,ppm_in,CHANGE);// put your setup code here, to run once:
  13.   t.every(20,channels_update);
  14.   t.every(25,date_print);
  15. }


  16. void ppm_in()
  17. {
  18.   if(digitalRead(2)==1&&flag_out==0)
  19.   {
  20.     h_now=micros();
  21.     flag_in=1;
  22.   }
  23.   if(digitalRead(2)==0&&flag_in==1&&flag_out==0)
  24.   {
  25.     l_now=micros();
  26.     ppm_date[ppm_flag]=l_now-h_now;
  27.     ppm_flag+=1;
  28.     flag_in=0;
  29.     if(ppm_flag==20)
  30.     {
  31.       ppm_flag=0;
  32.       
  33.       flag_out=1;
  34.       }
  35.     }
  36.   }


  37. void channels_update()
  38. {
  39.   if(flag_out==1)
  40.   {
  41.   ppm_i=0;
  42.      while(ppm_date[ppm_i]<3000)
  43.     {ppm_i++;}
  44.     if(ppm_i<=12)
  45.     {
  46.     for(int j=0;j<8;j++)
  47.     {
  48.       channels[j]=ppm_date[(ppm_i+1)+j]+400;
  49.       }
  50.     }
  51.      flag_out=0;
  52.   }
  53.   }

  54. void date_print()
  55. {
  56.   /**********打印输出原始数据**************/
  57.    /* for(int i=0;i<20;i++)
  58.   {
  59.     if(i<19)
  60.     {
  61.     Serial.print(ppm_date[i]);
  62.      Serial.print(" ");
  63.     }
  64.     if(i>18)
  65.     {
  66.       Serial.println(ppm_date[i]);
  67.       }
  68.     }*/
  69. /****************打印输出通道数据**********************/
  70. for(int i=0;i<8;i++)
  71.   {
  72.     if(i<7)
  73.     {
  74.     Serial.print(channels[i]);
  75.      Serial.print("   ");
  76.     }
  77.     if(i>6)
  78.     {
  79.       Serial.println(channels[i]);
  80.       }
  81.     }


  82.   }
  83. void loop() {

  84. t.update();
  85. }
复制代码
程序说明:在初始化中还必须将D2端口设置为输入模式,并且设置中断0:attachInterrupt(0,ppm_in,CHANGE)。其中ppm_flag用于控制采集脉冲的个数,并且将这些脉冲序列按顺序存放到数组ppm_date[]中;flag_in用于确保采集脉冲是从上升沿开始(因为上升沿和下降沿都会触发中断),它在上升沿处理中被置1,在下降沿处理中被置0,达到的目的就是在没有采集到上升沿时不对下降沿进行处理,因为我们要采集的是高电平的时间;flag_out用于判断是否完成了脉冲采集和是否完成了通道解算,当完成采集时它被置1,这个时候中断函数停止数据采集,只有等通道更新完毕后,它才会被置0,中断函数才会进行新一轮的数据采集,而在它为0的期间,也就是在数据采集的期间,不会进行通道更新。接下来从串口监视器观察原始数据,即采集到的ppm_date[]:
PPM原始数据.png
上图中,每一行数字即是完整ppm_date[]帧,从数据中可以看到,每一行都有两个比较大的数字:大于3000,这个就是我们要找的“同步脉冲”,找到一个之后,后面跟的8个数,就是要提取的通道数据。当然,从数据中能够看到,有时每两个“同步脉冲”之间偶尔会出现不足8个有效数据的情况,但这个影响暂且可以接受,我们可以设计更严密的通道更新程序将这样的数据帧丢弃。下面来看看1通道的数据,在试验过程中可以保持一通道(副翼)摇杆在中立位置(所有微调归0),此时可以看到得到的数据为1120左右,在这种情况下,该通道的实际输出脉宽(PWM)应为1520,这个我们可以对FUTABA接收机的一通道输出PWM进行采样验证,因此从ppm中获取的数据还应该加上400,这个就是低电平的持续时间即上面提到的“被砍掉的那一部分”,当然这个时间也可以用中断去采集ppm的低电平间隔得到。在程序中我直接给每个通道加了400,通过与之前PWM采集的数据进行对比,得出的结果是一致的。下面从串口监视器观察解算出来的通道数据channels[],channels[0]代表1通道,副翼通道:
PPM通道数据.png
至此,PPM信号解析完成,从实验中可以看出,FUTABA T10CHG的ppm输出确实为7个比例通道,第8个通道保持在中立位。这个试验主要是针对FUTABA T10CHG,对于其他品牌的遥控器我将进行进一步的试验,比如6通道的SPEKTRUM DX6I遥控器的PPM输出,也许还是有差别的吧.
发表于 2021-7-4 09:47 | 显示全部楼层
可以更具体地解释一下为什么要取20个脉冲作为一个周期
屏幕截图 2021-07-04 094624.jpg
用一张表来模拟我们存PPM的数组,以周期=9为例(忽略0号元素)
在读取的时候,同步脉冲可能出现在任意位置
而在同步脉冲的前后都存在一个完整的脉冲周期,设通道数为X
也就是说,如果同步脉冲前面有X个空位,或者后面有X个空位,那么就可以得到一组完整的脉冲
于是我们归纳出这个不等式
屏幕截图 2021-07-04 093032.jpg 屏幕截图 2021-07-04 093648.jpg
N_ahead,N_behind代表同步脉冲前后的空位个数,可取任意自然数
显然 屏幕截图 2021-07-04 094121.jpg
于是我们就要让ceiling((N-1)/2)>=x
于是可以解出N是奇数时,X=9,N至少要是19,那么加上0号元素就得出来20这个数字
N是偶数时,N就是18,因为不能分配的更加均匀
 楼主| 发表于 2021-7-8 22:38 | 显示全部楼层
wzzzq 发表于 2021-7-4 09:47
可以更具体地解释一下为什么要取20个脉冲作为一个周期

用一张表来模拟我们存PPM的数组,以周期=9为例(忽 ...

发表于 2022-3-9 21:50 | 显示全部楼层
这么牛的帖子没看顶吗?我新手还在研究怎么读取PWM呢
发表于 2022-3-10 17:37 | 显示全部楼层
你学会了吗?我还是不会
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-12-1 05:03 , Processed in 0.114240 second(s), 21 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表