|
又找到一点信息
花费了将近3天时间,挂掉一片AVR Mega8,终于完成了一个PPM转发的代码。
想要完成舵量输出,非常简单。光是输出想做到没有舵机抖动也很容易。但要做到舵量采集后再输出没有抖动就复杂了。
一共2ms的舵量信息,M8内置的8M频率,代码上稍微多执行几行、少执行几行,采集回来的舵量就变了。
时间都花在这上面了,本来第一天就完成了代码,在我的辉盛5克上没有发现抖动的问题。挺开心,心想蛮简单的。可换到辉盛9克上,就开始抖。而且很频繁,基本没不抖的时候。
反复修改软件,调整计时顺序,舵量输出顺序,甚至想用外部12M晶振提高速度。我的那片M8就是为这个挂掉的。熔丝位设置了外部晶振,但是没起来,ISP也进不去了。按照网上说的用外部有源晶振的方法倒是ISP能认了,但认的芯片型号不对。当时也没在意,直接修改熔丝位到内部晶振。结果可能由于数据M8认的有误,RESET脚给关掉了,而且内部频率也不是8M,倒像是1M的。这下彻底没着....:em25: 我的第一片M8就这样交待了。
反复试验了N种方法,最后决定必须使用硬件的定时器来确定舵量。然后修改代码,将定时器0作为舵量输出用,利用定时器2进行舵量采集。
流程是,在main里先完成一次舵量采集,完全根据定时器的计时信息决定舵量,没有使用变量累加计数。然后定时器0启动,输出一次完整舵量。
输出完成后,定时器0暂停计时,再交由main继续下一次采集舵量。就这样一直循环下去。
舵量的输出也采用了新方法,定时器在输出完一部分后,直接修改TIME0的计时器到下一个状态的触发时间点上。都取消了软件变量累加的计时方法。
效果还是不错的,舵机已经基本不抖了。为了做到更好,稍微牺牲了一点灵敏度,代码里将舵量和上次的采集结果进行比较,发现差异超过1时再更新。这样最后的效果已经和直接接到接收机上没什么差别了。
下面是代码,希望对想做同样东西的人能有些帮助:
//-------------------------------------------
//
// PPM 解码/转发代码
// Version 1.0
// cnmusic@163.net
// 使用AVR Mega8,使用内部8M晶振
//
//-------------------------------------------
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#define FEED_DOG asm("wdr");
#define NOP asm("nop");
#define INT_ON sei();
#define INT_OFF cli();
typedef unsigned char BOOL;
typedef unsigned char BYTE;
typedef unsigned char CHAR;
#define TRUE 1
#define FALSE 0
#define CHANNELCOUNT 1
#define TICK_COUNT_LEAD 70 // 前0.5ms对应的定时器0的64分频的计数数值,虽然不是实际的0.5ms,但这是我这里舵机能
// 承受的最小数值了
typedef struct tagChannelData
{
BYTE nChannelOutValue; // 舵量数据
BYTE nChannelOutStep; // 舵量的输出状态
}CHANNELDATA;
volatile CHANNELDATA g_ChannelData[CHANNELCOUNT] = {{0}};
volatile BOOL g_bOutAllComplete = TRUE;
volatile BYTE g_nOutIndex = 0;
// TIME0定时器中断
// 这个中断用来切换各个舵机端口的状态,包含3个部分:
// 前0.5ms的引导,中间最大2ms的舵量,以及后面的低电平部分
// 为了精确计时,使用TIME0的计数器,而没用软件进行变量累加计数
ISR(TIMER0_OVF_vect)
{
if (g_ChannelData[g_nOutIndex].nChannelOutStep == 0)
{
PORTD |= (1<<PD7);
TCNT0 = 0xFF - TICK_COUNT_LEAD;
g_ChannelData[g_nOutIndex].nChannelOutStep = 1;
}
else if (g_ChannelData[g_nOutIndex].nChannelOutStep == 1)
{
TCNT0 = 0xFF - g_ChannelData[g_nOutIndex].nChannelOutValue;
g_ChannelData[g_nOutIndex].nChannelOutStep = 2;
}
else
{
PORTD &= ~(1<<PD7);
g_nOutIndex++;
if (g_nOutIndex >= CHANNELCOUNT)
{
g_bOutAllComplete = TRUE;
TCCR0 = 0;
}
else
{
TCNT0 = 0xFF - 1;
}
}
}
int main()
{
BYTE nChannelValueFinal = 0;
DDRD = (1<<DDD2) | (1<<DDD3) | (1<<DDD7); // PD2,PD3是指示灯,PD7是舵机输出端口
PORTD &= ~((1<<PD6) | (1<<PD7));
PORTD |= (1<<PD3); // 指示灯亮
INT_ON; // 开总中断
// 定时器0设置
TIMSK |= (1<<TOIE0); // 允许中断
while (1)
{
// 下面的代码循环扫描所有舵机的输入端口,
// 由于是测试板,所以这里只有一个PD6端口用作输入
asm("nop");
// TIME2用来计数,看输入舵量是多大。
TCCR2 = (1<<CS22); // 64分频
// 一直等待直到高电平来临
while (!(PIND & (1<<PIND6)))
{
//asm("nop"); // 如果使用更高级别的优化,要保留这个语句,否则整个while会被优化掉
}
// 重新设置计数器,开始计时
TCNT2 = 0;
// 等待前引导的0.5ms结束
while (PIND & (1<<PIND6))
{
if (TCNT2 == TICK_COUNT_LEAD) // 计数数值达到0.5ms,计时器清零
{
TCNT2 = 0;
break;
}
}
// 一直等到高电平结束
while (PIND & (1<<PIND6))
{
//asm("nop");
}
// 高电平结束,根据计时信息将舵量换成数字信息。
TCCR2 = 0;
nChannelValueFinal = TCNT2 - 2; // 示波器显示,这么测量后的舵量信息和实际的有16uS的差异,所以这里减去一点
if ((abs((int)g_ChannelData[0].nChannelOutValue - (int)nChannelValueFinal)) > 1)
{
g_ChannelData[0].nChannelOutValue = nChannelValueFinal; // 保存最后的舵量信息
}
g_ChannelData[0].nChannelOutStep = 0; // 设置舵量输出标志
// 重起定时器0
g_nOutIndex = 0; // 从0通道开始输出舵量
TCNT0 = 0xFF - 1; // 延迟1,基本是立即触发中断
TCCR0 = (1<<CS00)|(1<<CS01); // 64分频,定时器开始工作
// 继续等待下一次定时器0完成所有通道的舵量输出
g_bOutAllComplete = FALSE;
while (!g_bOutAllComplete);
}
}
这种方法没有去管那个总脉冲时长,完全根据输入脉冲的长度决定。
缺点就是如果某个IO口没接接收器,那么主循环就会一直等待下去,而舵量就不会输出了。
所以在进行扫描前要先确定哪个端口有没有悬空。
这种方式不会造成漏采集,因为整个脉冲里,真正有用的部分非常短,都算上才2.5ms,剩下的17.5ms都是低电平。
所以我们就利用这个时间差完成采集过程。
示波器显示,我们输出的舵机控制信号紧接着接收机发出的信号,频率和正脉宽也和接收机给出的一致。舵机也基本认可,基本上没有抖动现象发生。
测试过天地飞的发射/接收机和ESKY的发射/接收机,OK。
舵机:
辉盛5g,9g
ESKY0500,0508
ELE ES03
效果还是不错的。:loveliness:
[ 本帖最后由 c_nmusic 于 2008-9-29 15:30 编辑 ] |
|