zoukankan      html  css  js  c++  java
  • 一文打尽PWM协议、PPM协议、PCM协议、SBUS协议、XBUS协议、DSM协议 | STM32的通用定时器TIM3实现PPM信号输出

    PWM、PPM、PCM、SBUS、XBUS、DSM都是接收机与其他设备通信的协议。
    请注意这里不要将遥控器和接收机之间的协议混淆。遥控器和接收机之间会采用某种协议来互相沟通,这些协议往往各个厂牌各自有一套且互不兼容。
    但接收机输出的信号是有通行标准的,我们这里讨论的就是接收机输出的信号。

    1. PWM协议

    PWM(Pulse Width Modulation,脉宽调制),在航模中主要用于舵机的控制。这是一种古老而通用的工业信号,是一种最常见的控制信号。该信号主要原理是通过周期性跳变的高低电平组成方波,来进行连续数据的输出。如下图所示:
    而航模常用的PWM信号,其实只使用了它的一部分功能,就是只用到高电平的宽度来进行信号的通信,而固定了周期,并且忽略了占空比参数。

    电机(电调)上用到PWM信号种类很多,我们这里对常见的PWM信号特征进行一个描述: 

    1、 PWM信号是一个周期性的方波信号,周期为20ms,也就是50Hz的刷新频率。 

    2、 PWM每一周期中的高电平持续时间为1~2ms(1000us~2000us),代表了油门控制量。一般四旋翼中1100us对应0油门,1900us对应满油门

    PWM的优势
    由于传输过程全部使用满电压传输,非0即1,很像数字信号, 所以他拥有了数字信号的抗干扰能力。
    脉宽的调节是连续的,使得它能够传输模拟信号。
    PWM信号的发生和采集都非常简单,现在的数字电路则使用计 数的方法产生和采集PWM信号。
    信号值与电压无关,这在电压不恒定的条件下非常有用,比如电 池电压会随消耗而降低,DCDC都会存在纹波等等,这些因素不会干扰信号的传输。
    PWM因为处理简单,在航模圈至今仍然广泛用以驱动舵机和固定翼飞机的电调等。其相对于PPM等协议最大的不同在于,它每条物理连线里只传输1路信号。换句话说,需要传输几个通道,就需要几组物理连线。

    2. PPM(CPPM)协议、PCM协议

    PPM(Pulse Position Modulation,脉冲位置调制,又称脉位调制)。下面是PPM协议的波形图:
    因为PWM每路只能传输一路信号,在分别直接驱动不同设备的时候(比如固定翼,每路各自驱动不同的舵机和电调)这没有任何问题。但在一些场合,我们并不需要直接驱动设备,而是需要先集中获取接收机的多个通道的值,再做其他用途时,
    比如将两个遥控器之间连接起来的教练模式,比如遥控器接电脑玩模拟器,当然还有我们玩多轴,要将接收机的信号传输给飞控时,每个通道一组物理连线的方式就显得非常的繁琐和没有必要。这时候PPM就是救星了。
    航模使用的PWM信号,高电平的持续时间在整个时间轴上所占的空间其实是很小的(假设高电平是信号),绝大部分的时间都是空白的。PPM简单的将多个通道的数值一个接一个合并进一个通道,用2个高电平之间的宽度来表示一个通道的值。
    PPM协议最多传输10个通道,使用一个定时器就可以轻松解决了。
    因为每一帧信号的尾部必须加入一个足够长的空白(显著超过一个正常PWM信号的宽度)来分隔前后两个信号,每一帧能传输的信号通道最多只能到10个。
    这在大部分的场合已经足够了,比如刚才说的教练模式/模拟器/多轴等。且PPM是一个通行标准,绝大多数厂牌的遥控/接收都是支持的。

    PCM(pulse-code modulation,脉冲编码调制,又称脉码调制)。这里顺便提一句,有些航模爱好者误将PPM编码说成是FM,其实这是两个不同的概念。前者指的是信号脉冲的编码方式,后者指的是高频电路的调制方式。

    航模遥控器发射电路的工作原理是:通过操纵发射机上的手柄,将电位器阻值的 变化信息送入编码电路。编码电路将其转换成一组脉冲编码信号(PPM或PCM)。这组脉冲编码信号经过高频调制电路(AM或FM)调制后,再经功放电路发送出去。PPM的编解码方式一般是使用积分电路来实现的,而PCM编解码则是用模/数(A/D)和数/模(D/A)转换技术实现的。

    航模遥控器中最常用的两种脉冲编码方式就是PPM和PCM;

    最常用的两种高频调制方式是FM调频和AM调幅;

    最常见的组合为PPM/AM脉位调制编码/调幅、PPM/FM脉位调制编码/调频、PPM/FM脉冲调制编码/调频三种形式。

    通常的PPM接收解码电路都由通用的数字集成电路组成,如 CD4013,CD4015等。对于这类电路来说,只要输入脉冲的上升沿达到一定的高度,都可以使其翻转。这样,一旦输入脉冲中含有干扰脉冲,就会造成输出混乱。由于干扰脉冲的数量和位置是随机的,因此在接收机输出端产生的效果就是“抖舵”。除此之外,因电位器接触不好而造成编码波形的畸变等原因,也会影响接收效果,造成“抖舵”。对于窄小的干扰脉冲,一般的PPM电路可以采用滤波的方式消除;而对于较宽的干扰脉冲,滤波电路就无能为力了。这就是为什么普通的PPM比例遥控设备,在强干扰的环境下或超出控制范围时会产生误动作的原因。尤其是在有同频干扰的情况下,模型往往会完全失控。

    3. SBUS(S-BUS/S.BUS)

    SBUS(Serial Bus,串行总线)。

    SBUS:每11个位(bit)表示一个通道数值的协议,串口通信,但是sbus的接收机通常是反向电平,连接到无人机时需要接电平反向器,大部分支持sbus的飞行控制板已经集成了反向器,直接将接收机连接到飞行控制器即可。

    XBUS:常规通信协议,支持18个通道,数据包较大,串口通信有两种模式,可以在遥控器的配置选项中配置。接收机无需做特殊配置。

    SBUS是一个串行通信协议,最早由日本厂商FUTABA(扶他爸~)引入,随后FrSky的很多接收机也开始支持,S.BUS是全数字化接口总线,数字化指的是该协议使用现有数字通信接口作为通信的硬件协议,使用专用的软件协议,这使得该设备非常适合在单片机系统中使用,也就是说适合与飞控连接。这也就是我为什么要将这个协议详细叙述的原因。总线是指他可以连接多个设备,这些设备通过一个Hub与这个总线相连,得到各自的控制信息。
    SBUS使用RS232C串口的硬件协议作为自己的硬件运行基础。 使用TTL电平,即3.3V。  使用负逻辑,即低电平为“1”,高电平为“0”。 采用100K的波特率(注意:不兼容波特率115200),8位数据位,两位停止位,偶效验,即8E2的串口通信。
    SBUS是一个接收机串行总线的输出,通过这根总线,可以获得遥控器上所有通道的数据。目前很多模型及无人机电子设备都支持SBUS总线的接入。使用SBUS总线获取通道数据,效率高,而且节省硬件资源,只需要一根线即可获取所有通道的数据。

    SBUS一帧数据的长度为25个字节:

    字节[0]:SBUS头,0x0F

    字节[1-22]:16个伺服通道,每个伺服通道采用11位编码

    字节[23]:          

    位7:数字通道17(0x80)          

    位6:数字通道18(0x40)          

    位5:丢帧(0x20)          

    位4:用来激活故障安全(0x10)          

    位0-3:n/a

    字节[24]:SBUS结束字节,0x00

     

    具体协议的格式如下:

            [数据头] [第一个字节] [第二个字节] ......[第二十二的字节] [标志位] [数据尾]

    数据头、标志位、数据尾 不携带信息,而且数据头和数据尾是固定的,数据头=0x0f; 数据尾=0x00; 

    数据头(1字节)+数据(22字节)+标志位(1字节)+数据尾(1字节)

    SBUS的每个RC通道值映射为:

    -100%= 173(相当于PWM伺服信号中的1000)

    0%= 992(相当于PWM伺服信号中的1500)

    100%= 1811(相当于PWM伺服信号中的2000)

    值得注意的有三点:

    (1)SBUS采用负逻辑,所以无论接收还是发送都要进行硬件取反(注意,一定要硬件取反),电路如下:

     STM32F0芯片内置了反相电路,所以芯片外围不用加。

    (2)SBUS有两种模式,

    a.高速模式:每4ms发送一次

    b.低速模式:每14ms发送一次

       就是说每间隔4ms或者14ms这个串口就发送25个字节的数据,这25个字节的数据最多可以包含16个信息。

    (3)100K的波特率不是标准波特率,一般串口工具都不能直接读取(所以不要直接用电脑调试,除非你的电脑写好了非标准串口),可以用单片机读取。

    编码原理

           一个信息是二进制的11bit,比如1111 1111 111就可以表示一个信息,一共16个这样的信息,按照顺序将这16个信息依次排成一串,得到一个176bit(11 *16)的数据,也就是22字节(176 / 8 = 22)的数据,再加上数据头数据尾校验位就组成了一个要通过串口传送的信息。每隔4或者14ms就传送一个这样的信息。

          所以这16个信息每一个所能表示的最大值是2^11 = 2048,也就是他的精度。

          标志位的高四位有特殊含义,第四位并没有使用,依照我的理解,第七位和第六位表示两个数字通道(通道17和18)信息(就是只有高低电平的通道,一般用来控制通断或者某个电机简单的启动或者停止,比如1表示启动电机0表示停止电机)。第五位表示帧丢失,接收机红色LED亮起,如果这一位为1,表示这一帧信号出问题了,接收机红色LED亮起。第四位表示故障保护激活,意思应该是说如果这一位为1,激活接受方故障保护。

         bit7 = ch17 = digital channel (0x80)
         bit6 = ch18 = digital channel (0x40)
         bit5 = Frame lost, equivalent red LED on receiver (0x20)
         bit4 = failsafe activated (0x10)
         bit3 = n/a
         bit2 = n/a
         bit1 = n/a
         bit0 = n/a

    代码实现可以查看笔者这篇文章:https://www.bilibili.com/read/cv5824207

    4. DSM2(DSMX)

    DSM(Digital Spread Spectrum Modulation,数字扩频调制)。
    DMS协议一共有三代: DSM、DSM2、DSMX。国内最常见的是DSM2,JR和Spectrum 的遥控器都支持。该协议也是一种串行协议,但是比S.BUS更加通用,使用的标准串口定义,所以市面上兼容接收机更加便宜,兼容的设备也更多,比如电直的三轴陀螺VBar就可以直接接受DSM2信号。
    但是该协议并不是一种总线化的协议,要靠接收机取把协议变为PWM来驱动舵机,DSM2接口也只能连接接收机和卫星接收机,不过对于飞控来说这个无所谓,反正也是一个接口连接到飞控就可以了。
    DSMX是DSM2的升级版,协议基本一样就是速率加快了。DSMX协议可以用于双向传输,即能够将飞机上的信息传回遥控器上在液晶屏显示,不过对于玩儿飞控这个功能不重要,有了电台和PC,这个意义不大。
     
    那么到底该用什么呢?
     
    ★.如果你是固定翼玩家,也无意在固定翼飞机上加飞控,那么其实这个问题对你来说不是问题:PWM。
    ★.如果你需要配置无线教练机或者无线模拟器,那么一个支持PPM输出的接收机可以省去一团乱麻的连线。如果普通休闲玩多轴,无论是航拍还是穿越,PPM也足够胜任。
    ★.如果你开始追求极限的穿越机表现,那也许你会开始能感受到S.BUS的低延迟带来的优势。或者你涉足功能丰富的正经航拍机,除了控制飞机,还要控制云台等等一系列其他附加设备时,S.BUS的多通道会给你带来很大便利。然而你需要寻找支持S.BUS的遥控接收组合,这也许意味着额外的投入。
     
    参考链接:
    http://www.5imx.com/portal.php?mod=view&aid=1351&_dsign=50c64f76
    https://mp.weixin.qq.com/s/wECoHtQYbEWaWJYLR7Ne7g
    https://mp.weixin.qq.com/s/Kt2ZssIrRyYbB-jxp1pwFw

    5. STM32实现PPM信号输出

    纸上得来终觉浅,绝知此事要躬行。

    道理讲了这么多,让我们上手实现一下PPM输出,这是我们J20航模遥控器要用到的一个功能模块,迟早要加进去。

     tim.c文件

    #include "tim.h"
    #include "main.h"
    
    #define PPM_NUM 18 //PPM数组中的个数,这里是8通道1+8*2+1
    
    u16 PPM_Array[PPM_NUM] = {500,1000,500,1000,500,500,500,1000, 500,1000,500,1000,500,1000,500,1000,500,8000};
    u16 PPM_Index = 0;//PPM数组索引号
    /*TIM3的中断初始化*/ 
    static void NVIC_Configuration(void)
    {
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3的中断号
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级1
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
    }
    
    /*PPM引脚初始化*/ 
    static void PPM_GPIO_Configuration(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);  //使能GPIO外设时钟
        //设置PPM引脚为推挽输出功能,输出脉冲波形
        GPIO_InitStructure.GPIO_Pin = PPM_Pin; //PPM引脚号
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
        GPIO_Init(PPM_GPIO_Port, &GPIO_InitStructure);//初始化PPM端口
    }
    /*TIM3初始化*/ 
    void PPM_Init(void)
    {
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);    //使能定时器3时钟
         
        TIM_TimeBaseStructure.TIM_Period = 500; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
        TIM_TimeBaseStructure.TIM_Prescaler = 71; //设置用来作为TIMx时钟频率除数的预分频值 
        TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
        TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
        TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//允许 更新 产生中断
        
        PPM_GPIO_Configuration(); //GPIO初始化
        NVIC_Configuration(); //中断初始化
        TIM_Cmd(TIM3, ENABLE); //使能TIM3
        PPM = 0;//前500us输出低电平
    }
    
    /*TIM3中断服务子程序*/
    void TIM3_IRQHandler(void)
    {  
        if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
        {   
            TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除标志位
            TIM3->ARR = PPM_Array[PPM_Index]-1;//更新TIM3的自动重装载值,减1是给后几行程序留时间
            PPM = !PPM;//更改电平
            PPM_Index++;
            if(PPM_Index>=PPM_NUM)
            {
                PPM_Index = 0;
            }
        }
    }

    tim.h文件

    #ifndef __TIM_H
    #define __TIM_H
    
    #include "main.h"
    
    void PPM_Init(void);
    
    #endif
     

    main.c文件

    /*
     * 定时器TIM3实现PPM输出
     * 具体说明见Doc/ReadMe.txt文档
    */
    
    #include "main.h"
    
    /*变量定义*/
    
    
    /*程序开始时运行一次的代码*/
    void setup(void)
    {
        delay_init();//初始化延时函数
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2,2位抢占优先级和2位子优先级
        usart_init(115200);//初始化串口1(波特率为115200,8个数据位,无校验,1个停止位)
        PPM_Init();
    }
    
    /*不断循环执行的代码*/
    void loop(void)
    {
        
    }
    
    int main()
    {
        setup();
        while(1){loop();}
    }

    main.h文件

    #ifndef __MAIN_H
    #define __MAIN_H
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    /*main.h统一定义引脚,便于移植*/
    #include "stm32f10x.h"
    #include "sys.h"
    #include "delay.h"
    #include "usart.h"
    #include "tim.h"
    
    /*引脚定义*/
    
    #define PPM_Pin         GPIO_Pin_13
    #define PPM_GPIO_Port     GPIOC        
    #define PPM                PCout(13)
    
    #ifdef __cplusplus
    }
    #endif
    #endif
     

    工程文件下载:https://files.cnblogs.com/files/cai-zi/PPM.zip

    用逻辑分析仪接在PC13,查看波形输出如下,看起来时间还是很准确的。

     

  • 相关阅读:
    文档驱动开发模式在 AIMS 中的应用与实践
    软件“美不美”,UI测试一下就知道
    做运维,送你7个常用的服务器资源监控工具
    掌握ROMA Compose,报表清单不秃头
    技术干货丨隐私保护下的迁移算法
    业务爆发式增长,音视频服务如何做好质量监控与优化?
    MyBatis中SQL语句优化小结
    Go语言微服务开发框架:Go chassis
    揭开KPI异常检测顶级AI模型面纱
    激光雷达lidar与点云数据
  • 原文地址:https://www.cnblogs.com/cai-zi/p/14110204.html
Copyright © 2011-2022 走看看