D调的华丽 发表于 2017-10-3 21:15:28

6路接收机PWM信号融合为PPM信号

本帖最后由 D调的华丽 于 2017-10-3 21:15 编辑

    最近固定翼入魔,练模拟器的时候,有个很不爽的事(因为我是把电脑接到大电视上玩模拟器),遥控器上要拖一根教练机接口的线。其实这个线输出的是PPM六通道融合的信号(当然,如果遥控器是10通道的就是10通道融合的)。作为一个玩STM32的人,怎么能容忍这种拖着一根线玩模拟器呢,随便找个开发板,把接收机的6通道信号分别如下连接:通道1--->PA0;
通道2--->PA1;
通道3--->PA2;
通道4--->PA3;
通道5--->PA0;
通道6--->PA4;
凤凰模拟器--->PB1和GND;
别忘了从开发板上供一个+5V的电源给接收机。


OK,先理理程序怎么设计。
接收机如图


第一排引脚输出的其实是一组舵机控制信号,20ms周期,高电平脉宽1000us~2000us对应油门0%~100%(具体对应关系可能略微差别)。而模拟器从遥控器教练输出端口输出来的其实是6通道融合后的PPM信号,具体看下图



所以现在要做的就是,把遥控器的6路PWM信号的每周期高电平脉宽测量出来,并融合成一个PPM信号,要测量的同时要保证其实时性,我们所有通道做最大油门和最小油门的情况分析,如下图
左边全部最小油门,右边最大油门


上图可以看出, 要想在最后一个通道数据测量完成之后再对第六通道的PPM进行融合处理的最佳时间为第3个通道下降沿发生时进行PPM融合处理,这样保证在接收到的信号与融合的信号只差半个周期(也就是10ms左右),如果本周期测量,下周期融合输出,会滞后一个周期。

六个通道的输入信号不会同时发生,是依次发生的(不信可以自己用逻辑分析仪或者示波器看~)

有了这个思路,程序基本上就清楚了
利用外部中断(上升下降触发模式)进中断记录systick定时器10us的计数获得每通道的高电平时长(单位10us),因为信号精度0.1ms,10us的systick定时器可以做到非常精准还原。
利用另一个系统定时器计时还原输出信号。具体程序如下:

1:端口配置GPIO.H中定义跳变宏

[
#define digitalHi(p,i)                              {p->BSRR=i;}                        //设置高电平      
#define digitalLo(p,i)                              {p->BRR      =i;}                              //设置低电平

#define PPM_ON                digitalHi(GPIOB,GPIO_Pin_1)
#define PPM_OFF                        digitalLo(GPIOB,GPIO_Pin_1)


2:GPIO.C中初始化函数


void GPIO_Config(void)
{               
                /*定义一个GPIO_InitTypeDef类型的结构体*/
                GPIO_InitTypeDef GPIO_InitStructure;

                /*开启GPIOB和GPIOF的外设时钟*/
                RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

                /*选择要控制的GPIOB引脚*/                                                                                                                           
                GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;      

                /*设置引脚模式为通用推挽输出*/
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

                /*设置引脚速率为50MHz */   
                GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

                /*调用库函数,初始化GPIOB0*/
                GPIO_Init(GPIOB, &GPIO_InitStructure);      
               
}
]


3:设置滴答定时器systick.h中

]#ifndef __SYSTICK_H
#define __SYSTICK_H

#include "stm32f10x.h"

void SysTick_Init(void);

#endif ]

4:systick.c中配置初始化

]void SysTick_Init(void)
{
      /* SystemFrequency / 1000    1ms中断一次
         * SystemFrequency / 100000         10us中断一次
         * SystemFrequency / 1000000 1us中断一次
         */
//      if (SysTick_Config(SystemFrequency / 100000))      // ST3.0.0库版本
      if (SysTick_Config(SystemCoreClock / 100000))      // ST3.5.0库版本
      {
                /* Capture error */
                while (1);
      }

//关闭滴答定时器,初始化完后再开启
      SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}]

5:中断初始化配置.h中的申明就不写了,直接配置函数如下:

]static void NVIC_Configuration(void)
{

      
NVIC_InitTypeDef NVIC_InitStructure;

      
                EXTI_DeInit();
      
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

/* 配置中断源 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//PA0的中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;    //优先级,别设置太高
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 13;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
      
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;//PA1的中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
      
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//PA2的中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 15;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 15;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
      
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;   //PA3的中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 16;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 16;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
      
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;   //PA4的中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 17;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 17;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
      
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;   //PA5的中断线
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 18;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 18;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
      
}]

]void EXTI_PA0_5_Config(void)
{
      GPIO_InitTypeDef GPIO_InitStructure;
      EXTI_InitTypeDef EXTI_InitStructure;

      /* config the extiline clock and AFIO clock */
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);
                                                                                                
      /* config the NVIC */
      NVIC_Configuration();

      /* EXTI line gpio config*/      
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;      
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;         // 下拉输入,因为是测高电平脉冲
GPIO_Init(GPIOA, &GPIO_InitStructure);

      /* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //上升下降中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
      
                /* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
      
      /* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
      
      /* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
      
      /* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);
EXTI_InitStructure.EXTI_Line = EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
      
      /* EXTI line mode config */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
EXTI_InitStructure.EXTI_Line = EXTI_Line5;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
]


6:输出用的系统定时器设置

]/// TIM2中断优先级配置
void TIM2_NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
   
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);                                                                                                         
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;         
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;      
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


/*
* TIM_Period / Auto Reload Register(ARR) = 1000   TIM_Prescaler--71
* 中断周期为 = 1/(72MHZ /72) * 10 = 10us
*
* TIMxCLK/CK_PSC --> TIMxCNT --> TIM_Period(ARR) --> 中断 且TIMxCNT重置为0重新计数
*/
void TIM2_Configuration(void)
{
    TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
               
                /* 设置TIM2CLK 为 72MHZ */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);
    //TIM_DeInit(TIM2);
      
      /* 自动重装载寄存器周期的值(计数值) */
    TIM_TimeBaseStructure.TIM_Period=10;
      
    /* 累计 TIM_Period个频率后产生一个更新或者中断 */
          /* 时钟预分频数为72 */
    TIM_TimeBaseStructure.TIM_Prescaler= 71;
      
                /* 对外部时钟进行采样的时钟分频,这里没有用到 */
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
      
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
      
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
      
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
               
    TIM_Cmd(TIM2, ENABLE);                                                                                                                                                
   
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , DISABLE);                /*先关闭等待使用*/   
}]


7:具体的中断函数写到main.c中,因为代码量不大-^-
]
uint8_t i;//输出开关
volatile u32 time = 0;// 输出计时变量
__IO u32 timenow;   //输入信号暂存测量值
__IO u32 timeCh1;   //通道测量值
__IO u32 timeCh2;
__IO u32 timeCh3;
__IO u32 timeCh4;
__IO u32 timeCh5;
__IO u32 timeCh6;

void EXTI0_IRQHandler(void)    //CH1通道中断处理
{
      if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)){timenow = 0;SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk; }// 判断进入后引脚是高电平,证明上升沿进入,暂存值清0,开启滴答定时器
      if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)){ timeCh1 = timenow;}                                                    // 中断进入后引脚低电平,证明下跳沿进入,一个高电平测量结束,存入当前通道的滴答累加值
      EXTI_ClearITPendingBit(EXTI_Line0);
}

void EXTI1_IRQHandler(void)   
{
      if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)){timenow = 0;SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk; }
      if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)){ timeCh2 = timenow;}
      EXTI_ClearITPendingBit(EXTI_Line1);
}

void EXTI2_IRQHandler(void)
{
      if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2)){timenow = 0;SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk; }
      if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2)){ timeCh3 = timenow; i=1; }                                                // 本处多了i = 1;表示3通道结束时开启PPM融合输出开关
      EXTI_ClearITPendingBit(EXTI_Line2);
}

void EXTI3_IRQHandler(void)
{
      if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3)){timenow = 0;SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk; }
      if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3)){ timeCh4 = timenow;}
      EXTI_ClearITPendingBit(EXTI_Line3);
}

void EXTI4_IRQHandler(void)
{
      if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)){timenow = 0;SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk; }
      if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)){ timeCh5 = timenow;}
      EXTI_ClearITPendingBit(EXTI_Line4);
}

void EXTI9_5_IRQHandler(void)
{
      if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)){timenow = 0;SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk; }
      if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)){ timeCh6 = timenow;}
      EXTI_ClearITPendingBit(EXTI_Line5);
}


oid SysTick_Handler(void)
{
      timenow++;    //滴答定时器开启后进入中断,脉宽测量计数,单位10us
}


void TIM2_IRQHandler(void)    //输出定时器中断,用于输出时输出信号计时
{
      if ( TIM_GetITStatus(TIM2 , TIM_IT_Update) != RESET )
      {      
                time++;                                 
                TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);                  
      }                        
}




int main(void)
{      
      GPIO_Config();/* 端口初始化 */

      //USART1_Config();如果要调试,开启串口

      EXTI_PA0_5_Config();   //中断配置

      SysTick_Init();/* 配置SysTick 为10us中断一次 */
      
      TIM2_NVIC_Configuration();//输出计时定时器中断配置
      TIM2_Configuration();          //输出定时器配置
      
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);    //开启输出定时器
      
      PPM_ON;   //拉高输出(PPM信号默认高电平)
      while(1)
      {
                if(i == 1)    //第3通道测量完成开启输出
                {
                        time = 0;   //输出计时清0
                        PPM_OFF;//拉低输出
                        while(time<=40){}//等待0.4ms的信号头
                        
                        PPM_ON;       //开始第一通道信号
                        time = 0;      //同时清0计时器
                        while(time < timeCh1-30){};   //1通道高电平时间 = PWM测量脉宽-0.3ms信号分界
                        PPM_OFF;       //时间到拉低
                        time = 0;         //同事清0计时器
                              
                        while(time<=30){}   //0.3ms低电平
                        PPM_ON;                  //开始第2通道信号
                        while(time < timeCh2-30){};
                        PPM_OFF;
                        time = 0;      
                              
                        while(time<=30){}
                        PPM_ON;      
                        while(time < timeCh3-30){};
                        PPM_OFF;
                        time = 0;      
                              
                        while(time<=30){}
                        PPM_ON;      
                        while(time < timeCh4-30){};
                        PPM_OFF;
                        time = 0;
                              
                        while(time<=30){}
                        PPM_ON;      
                        while(time < timeCh5 -20){};      //本处理论应该是-30,但是根据实际波形微调成-20,
                        PPM_OFF;
                        time = 0;      
                              
                        while(time<=30){}
                        PPM_ON;
                        while(time < timeCh6-20){};
                        PPM_OFF;
                        time = 0;      
                        while(time<=30){}
                        PPM_ON;            //信号结束,从新拉高输出,一个信号完成

                              
//                        printf("--- %d --- %d --- %d --- %d --- %d --- %d--- time: %d\r\n",timeCh1,timeCh2,timeCh3,timeCh4,timeCh5,timeCh6,time);      //如果要调试,开启
                        i = 0;//一组输出完成,从新锁定
                }
               
      }   
}



]


基本代码就完成了,实际最大最小值情况的输出波形如下,蓝色为PPM信号,黄色为第一通道的波形,可以看出,每个周期完结前PPM融合也完成了。以后玩模拟器再也不用拖一根线了;P。其实另外的用处就是,某宝上卖的PPM信号编码器,玩航模的基本都不陌生,有些飞控只有PPM信号输入,但是很多遥控器都没有PPM输出,之后多通道的PWM信号输出,所以就要一块PWM编码PPM的模块来实现。


Damn_intuition 发表于 2018-5-4 10:33:10

刚看到,不错! 好文
页: [1]
查看完整版本: 6路接收机PWM信号融合为PPM信号