初级会员
- 积分
- 53
- 金钱
- 53
- 注册时间
- 2017-9-28
- 在线时间
- 4 小时
|
本帖最后由 D调的华丽 于 2017-10-3 20:37 编辑
最近固定翼入魔,练模拟器的时候,有个很不爽的事(因为我是把电脑接到大电视上玩模拟器),遥控器上要拖一根教练机接口的线。其实这个线输出的是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中定义跳变宏
[mw_shl_code=c,true]
#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)
[/mw_shl_code]
2:GPIO.C中初始化函数
[mw_shl_code=c,true]
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);
}[/mw_shl_code]
3:设置滴答定时器systick.h中
[mw_shl_code=c,true]#ifndef __SYSTICK_H
#define __SYSTICK_H
#include "stm32f10x.h"
void SysTick_Init(void);
#endif [/mw_shl_code]
4:systick.c中配置初始化
[mw_shl_code=c,true]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;
}[/mw_shl_code]
5:中断初始化配置.h中的申明就不写了,直接配置函数如下:
[mw_shl_code=c,true]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);
}[/mw_shl_code]
[mw_shl_code=c,true]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);
}
[/mw_shl_code]
6:输出用的系统定时器设置
[mw_shl_code=c,true]/// 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_TimeBaseInitTypeDef TIM_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); /*先关闭等待使用*/
}[/mw_shl_code]
7:具体的中断函数写到main.c中,因为代码量不大-^-
[mw_shl_code=c,true]
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; //一组输出完成,从新锁定
}
}
}
[/mw_shl_code]
基本代码就完成了,实际最大最小值情况的输出波形如下,蓝色为PPM信号,黄色为第一通道的波形,可以看出,每个周期完结前PPM融合也完成了。以后玩模拟器再也不用拖一根线了。其实另外的用处就是,某宝上卖的PPM信号编码器,玩航模的基本都不陌生,有些飞控只有PPM信号输入,但是很多遥控器都没有PPM输出,之后多通道的PWM信号输出,所以就要一块PWM编码PPM的模块来实现。
|
|