admvip 发表于 2011-2-14 09:55:20

一个解码RC遥控器的程序设计思路

最近写了一个解调航模遥控器PWM信号,控制舵机的程序。
遥控器是福特吧的4VF四通道的控,单片机用的是AVR M8。
也看过其他人写的同时输出多路PWM信号的程序,需要外接硬件扩展,我写的程序用了另外一种方法来实现,已经在面包板上实现了控制4路舵机,示波器显示的波形也很正常,但是不知道这个生成多路PWM的算法到底好不好,有没有漏洞或设计不严谨的地方,我是个单片机初学者,希望高手能指点一下。

生成多路PWM的编程思路
使用了定时器0溢出中断和ICP1捕获中断,捕获中断负责接收解调输入的多路混合的PWM信号,定时器0溢出中断负责输出各路独立的PWM控制信号,定时器0和1都设置为64分频,主频8M时,8uS一个setp,用捕获中断来解码输入的信号,将每个通道的脉宽数据保存到到一个全局变量数组中,确认一帧多通道数据接收完毕后打开定时器0中断,在定时器0中断里(8uS一次溢出中断)每次将各通道的采集数据减1,然后判断各通道数据,大于0的响应输出端口输出高电平,等于0的就输出低电平,全部都为0了就关闭定时器0中断,等待ICP中断再次打开它。

实际我试验了4个通道,没有问题,各通道波形都很好,在输入的PWM信号第四个脉冲结束后各端口就同时输出单独的舵机控制PWM了,但是总觉得程序设计中有漏洞,但又不知道在那里,所以写出来请有经验的大大们拍拍砖。以上只是编程的思路,如果需要程序我再贴上,顺便说一下,我是业余玩单片机的,不是职业的。

admvip 发表于 2011-2-15 08:01:43

一直觉得设计思路有问题,想了一天,昨天软件仿真了一下(仿真用的很少),终于发现问题了,犯了一个大的错误,就是在定时器0中断里处理波形输出,原以为8us的中断够执行64条指令,谁知一仿真,仅仅输出4路控制波形耗时大大超出了8us,犯了中断里只能执行简短代码的大忌。今天抽时间将波形输出的代码转化为函数,放到主程序里执行,中断里就放一个计数器用于计算输出的时间,别的什么也不能干了。

admvip 发表于 2011-2-15 15:23:04

已经将PWM输出改成函数了,一切正常,中断里就放了一个计数器变量累加,别的什么也不干,中断不会超时了。
附上代码:
中断.h
----------------
/************************************************************************
文件名称:TimeCount.H
修改日期:2011/02/11
创建人:admvip

定时器0,定时器1的初始化和中断处理函数
************************************************************************/

#ifndef                 __TIMECOUNT_H__
#define                 __TIMECOUNT_H__

#include <iom8v.h>
#include <AVRdef.h> //7.22新版macros.h替换文件

/*常数宏定义*/
#ifndef TRUE
#define TRUE                        1
#endif

#ifndef FALSE
#define FALSE                        0
#endif

#ifndef NULL
#define NULL        0
#endif

/*定义舵机信号输出端口*/
#define PWMOUTPORT                PORTD
#define CH1                                PD2
#define CH2                                PD3
#define CH3                                PD4
#define CH4                                PD5
#define CH5                                PD6
#define CH6                                PD7


/*定义_解码通道数量*/
#define CHANNELCOUNT        5

/*0.5ms前导脉冲宽度常量*/
/*8M主频64分频计数器一个SETP是8uS,0.5ms大致是62.5个SETP*/
#define PULSEMinWIDTH                2
#define PULSEMaxWIDTH                325


/*模块变量外部引用声明*/
extern volatile unsigned char         g_ChannelData[];                  //通道数据存储数组
extern volatile unsigned char        g_bReceiveStart;                  //通道数据接收开始标志
extern volatile unsigned char        g_bInAllComplete;                  //输入波形采集完成标志
extern volatile unsigned char        g_nInIndex;                                  //当前输入通道号
extern volatile unsigned char        g_nOutIndex;                          //当前输出通道号
extern volatile unsigned int           g_nPwmWidth;                          //被检测脉冲宽度


/*模块函数外部引用声明*/
extern void TimeCount_init(void);
extern void PWM_OUT(void);

#endif


定时器.c
-----------
/************************************************************************
文件名称:TimeCount.C
修改日期:2011/02/11
创建人:admvip

定时器0,定时器1的初始化和中断处理函数
************************************************************************/

#include <iom8v.h>
#include <AVRdef.h>
#include "TimeCount.h"


/*变量定义区*/
volatile unsigned charg_ChannelData;        //通道数据存储数组
volatile unsigned charg_bInAllComplete= FALSE;                        //输入波形采集完成标志
volatile unsigned charg_bOutAllComplete = TRUE;                //输出波形发送完成标志
volatile unsigned charg_nInIndex= 0;                                //当前输入通道号
volatile unsigned charg_nOutIndex = 0;                                //当前输出通道号
volatile unsigned int   g_nPwmWidth = 0;                                //被检测脉冲宽度
volatile unsigned charg_bReceiveStart   = FALSE;                //通道数据接收开始标志
volatile unsigned int   s_nTC0_Count;
/********************************************/
//TIMER0 initialize - prescale:64
// desired value: 8uSec
// actual value:8.000uSec (0.0%)
static void timer0_init(void)
{
        TCCR0 = 0x00;        //stop
        TCNT0 = 0xFF;        //set count
        //TCCR0 = 0x03; //start timer
}

//TIMER1 initialize - prescale:64
// WGM: 0) Normal, TOP=0xFFFF
// Setp value:8.000uSec (0.0%)
static void timer1_init(void)
{
        TCCR1B = 0x00; //stop
        TCNT1H = 0x00; //setup
        TCNT1L = 0x00;
        OCR1AH = 0x00;
        OCR1AL = 0x01;
        OCR1BH = 0x00;
        OCR1BL = 0x01;
        ICR1H= 0x00;
        ICR1L= 0x01;
        TCCR1A = 0x00;
        //TCCR1B bit6:1上升沿捕获bit6:0下降沿捕获
        //TCCR1B |= BIT(6);上升沿捕获   TCCR1B &= ~BIT(6);下降沿捕获
        //根据需要可在捕获中断内变换
        TCCR1B = 0x43; //start Timer
}

void TimeCount_init(void)
{
        timer0_init();
        timer1_init();
        //TIMSK bit5:1使能TC1外部电平捕获bit5:0禁止TC1外部电平捕获
        TIMSK = 0x21;        //timer interrupt sources
        DDRD |= 0xFC;        // 设置PD2-PD7为舵机信号输出端口
}

/***********************************************/

/***********************************************
        TC0溢出中断负责各通道PWM波形输出,每8us刷新一
        次,中断内检测各通道数据,到达脉宽设定的通道结
        束PWM输出。全部通道发送完毕后所有端口置低电平,
        同时关闭TC0溢出中断,等待下一次数据输出。
***********************************************/
#pragma interrupt_handler timer0_ovf_isr:iv_TIM0_OVF
void timer0_ovf_isr(void)
{

        TCNT0 = 0xFF;        //reload counter value
        s_nTC0_Count++;

}



/***********************************************
        TC1输入捕获中断负责接收RC送来的PWM连续脉冲,
        通过检测各通道的脉冲宽度来确定各通道的PWM数据,
        有效通道脉冲全部接收完毕后打开TC0溢出中断实现
        各通道的PWM波形分离输出。
***********************************************/
#pragma interrupt_handler timer1_capt_isr:iv_TIM1_CAPT
void timer1_capt_isr(void)
{
        //timer 1 input capture event, read (int)value in ICR1 using;
        // value=ICR1L;            //Read low byte first (important)
        // value|=(int)ICR1H << 8; //Read high byte and shift into top byte
        if (TCCR1B & BIT(6))        // 是否为上升沿触发?
        {
                TCNT1   = 0;               //TC1计数器归零
                TCCR1B &= ~BIT(6); //修改为下降沿触发

        } else
        {
                g_nPwmWidth = ICR1;                // 下降沿触发后保存有效的脉宽数据
                TCCR1B |= BIT(6);                        // 修改为上升沿触发,为下一次脉冲做准备

                if (g_nPwmWidth > 625)        // 判断是否为超过5ms的脉冲,确定通道数据是否开始接收
                {
                        g_bReceiveStart = TRUE;        // 接收开始标志置位
                        g_nInIndex = 0;                                // 输入索引归零
                } else
                {
                        if (g_bReceiveStart)        // 判断接收开始标志置位,开始接受有效通道数据
                        {
                                g_ChannelData = (unsigned char) g_nPwmWidth;
                                if (g_nInIndex >= CHANNELCOUNT)        // 检测有效通道接收是否完成
                                {
                                        g_bReceiveStart= FALSE;               // 接收开始标志清零
                                        g_bInAllComplete = TRUE;       // 接收完成标志置位
                                        TCCR0 = 0x03;                                               // 启动TC0,准备信号输出
                                }
                        }
                }//if (g_nPwmWidth > 625) End

        }//if (TCCR1B & BIT(6)) End

}

void PWM_OUT(void)
{
        s_nTC0_Count = 0;                               //TC0计数器变量归零
        while (g_bInAllComplete)
        {
                if (s_nTC0_Count < PULSEMaxWIDTH)
                {
                        for (g_nOutIndex = 0; g_nOutIndex < CHANNELCOUNT; g_nOutIndex++)
                        {
                                if (g_ChannelData > s_nTC0_Count)
                                {
                                        //按脉宽数据设置输出高电平,+2是为了调整端口索引和实际端口的差别,改变硬件连接时需要调整
                                        PWMOUTPORT |= BIT(g_nOutIndex+2);
                                } else
                                {
                                        //达到脉宽的通道电平置低
                                        PWMOUTPORT &= ~BIT(g_nOutIndex+2);
                                }//if (g_ChannelData > s_nTC0_Count) End
                        }
                } else
                {
                        // 2.5ms时间到,所有舵机输出置低电平,结束一帧通道波形输出
                        PWMOUTPORT &= ~(BIT(CH1) | BIT(CH2) | BIT(CH3) | BIT(CH4) | BIT(CH5) | BIT(CH6));
                        if (s_nTC0_Count > 375)
                        {
                                //TCCR1B = 0x43; //start Timer
                                s_nTC0_Count = 0;        //TC0计数器变量归零
                                g_bInAllComplete = FALSE;
                                TCCR0 = 0x00;        //关闭TC0定时器,等待下一次输出
                        }
                }//if (s_nTC0_Count < PULSEMaxWIDTH) End
        }
}


主程序.c
---------------------
/****************************************************
       RC 4VF模型遥控器PWM单片机解码与控制
-----------------------------------------------------
MCU    :AVR M8
主    频 :8M
编制日期 :20110211
最后修订 :20110215
程序版本 :Ver1.1
-----------------------------------------------------
程序概述:
        使用M8的TC1的外部触发分别测量RC PWM信号的各个脉冲
宽度,用测量得到的数据按通道分别输出相应控制舵机能够
是别的脉冲信号,实现对RC PWM的信号解调。
    RC PWM参数请在TimeCount.h文件里配置。
    程序借鉴了部分徐江的控制代码,特此声明。
-----------------------------------------------------
程序功能模块:
主程序模块
定时中断处理模块
串口调试模块
-----------------------------------------------------
RC PWM解码器硬件连接
AVR M8 MCU
-----------
PB0    #14      ICP输入 <-------- 4VF PWM 输出信号
PD0    #2       RXD输入 <-------- 串口数据输入
PD1    #3       TXD输出 --------> 串口数据输出
PD2    #4               --------> CH1舵机控制输出
PD3           #5                                --------> CH2舵机控制输出
PD4           #6                                --------> CH3舵机控制输出
PD5           #11                                --------> CH4舵机控制输出
PD6           #12                                --------> CH5舵机控制输出
PD7           #13                                --------> CH6舵机控制输出
-----------------------------------------------------
程序修订记录:
2011.02.12
修改了中断捕获部分的代码,增加了几个标志变量,便于程
序进程控制。将PWM的配置数据和中断使用的全局变量移至
TimeCount.c文件中,在TimeCount.h中做了引用声明。
经过上机调试,发现捕获中断不能被关闭,否则会漏抓一帧
数据,导致输出不连续,分管输出信号的TC0可以在不使用
的时候关闭掉,减轻CPU的运算负担。

2011.02.15
将中断处理PWM输出改为函数处理PWM输出,在主程序中循环
调用PWM_OUT()函数即可实现连续PWM控制波形输出。
****************************************************/

#include "config.h"


void port_init(void)
{
PORTB = 0x00;
DDRB= 0x00;
PORTC = 0x00; //m103 output only
DDRC= 0x00;
PORTD = 0x00;
DDRD= 0x00;
}



//call this routine to initialize all peripherals
void init_devices(void)
{
//stop errant interrupts until set up
CLI(); //disable all interrupts
port_init();
Uart_Init();
TimeCount_init();


MCUCR = 0x00;
GICR= 0x00;
SEI(); //re-enable interrupts
//all peripherals are now initialized
}

void main (void)
{
init_devices();

while (1)
{
    PWM_OUT();
}
}

仅供大家参考!

isme00174 发表于 2011-2-22 21:46:24

学习ing。。。。。。。。。。。。。。。。。。。。。

zhu1982lin 发表于 2011-3-4 10:49:06

好东西,mark一下.

mosidao 发表于 2011-4-23 14:41:01

mark

HYLG 发表于 2011-4-23 15:28:59

这个看来是模拟的吧.
直接上数字的吧.

ndust 发表于 2011-4-23 22:17:57

jh

clqfly 发表于 2011-4-23 23:08:36

mark

xxjs 发表于 2011-6-27 23:30:39

mark

cqlutao 发表于 2011-7-4 19:08:53

好贴,留个名先.

clqfly 发表于 2011-7-4 20:34:43

玩航模的路过,mark

mafeimafei 发表于 2014-11-27 17:21:26

路过,mark.
页: [1]
查看完整版本: 一个解码RC遥控器的程序设计思路