我正在处理基于 arduino 的巨型四轴飞行器,并尝试为 4 个电机制作 PWM 频率 - 每个电机 400hz。
我找到了一个有趣的解决方案,其中使用4个ATmega2560 16位定时器通过PWM控制4个电调,使其可以达到400hz频率。700至2000μs是ESC正在处理的正常脉冲宽度。
1 秒/REFRESH_INTERVAL = 1/0.0025 = 400Hz。
this is servo.h lib:
#define MIN_PULSE_WIDTH 700 // the shortest pulse sent to a servo
#define MAX_PULSE_WIDTH 2000 // the longest pulse sent to a servo
#define DEFAULT_PULSE_WIDTH 1000 // default pulse width when servo is attached
#define REFRESH_INTERVAL 2500 // minimum time to refresh servos in microseconds
#define SERVOS_PER_TIMER 1 // the maximum number of servos controlled by one timer
#define MAX_SERVOS (_Nbr_16timers * SERVOS_PER_TIMER)
问题是要使其工作,每个PWM应使用1个16位定时器进行控制。换句话说,例如,1 个计时器上的 2 个 escs 将给出 200hz。所以所有的16位定时器都忙于控制4个ESC,但我仍然需要从接收器读取输入PPM。为此,我至少需要一个我不再拥有的 16 位计时器。
它仍然是一个 8 位定时器空闲位,它只能读取 0..255 个数字,而正常的数字 escs 操作是 1000..2000 等等。
那么,如果我使用相同的 16 位定时器进行 pwm 和 ppm 读数,会发生什么?行得通吗?它会大大降低速度吗?
我有arduino与Raspberry Pi配对工作,它控制数据过滤,调试和其他东西,将ppm读数移动到Raspberry更好吗?
回答您的一个问题:
那么如果我对 pwm 和 ppm 使用相同的 16 位计时器会发生什么 读数?行得通吗?
是的。当您的引脚更改中断触发时,您只需读取当前的 TCNT 值即可了解自上次更改以来已经过去了多长时间。这不会以任何方式干扰定时器的硬件 PWM 操作。
它会大大降低速度吗?
不。PWM 由专用硬件完成,同时运行的软件操作不会影响其速度,您可能为相应计时器激活的任何 ISR 也不会影响。因此,您可以让定时器根据需要生成PWM,并且仍然使用它来a)从中读取当前计数器值,b)将输出比较和/或溢出ISR挂在它上面以创建软件扩展的定时器。
编辑以回应您的评论:
请注意,TCNT寄存器中的实际值是任何时刻的当前定时器(时钟周期)计数,无论PWM是否处于活动状态。此外,定时器OVerflow中断(TOV)可以在任何模式下使用。这两个属性允许通过以下步骤为任意其他时间测量任务制作软件扩展计时器:
- 为要使用的计时器
- /计数器安装并激活计时器溢出中断。在 ISR 中,您基本上只需增加一个(易失性!)全局变量(例如
timer1OvfCount
),这有效地计算计时器溢出,从而扩展实际计时器范围。然后,可以将当前绝对即时报价计数计算为timer1OvfCount * topTimerValue + TCNTx
。
当事件发生时,例如 - 一个引脚上的上升沿,在处理例程(例如引脚更改 ISR)中,您读取当前定时器/外器(TCNT)值并
timer1OvfCount
并将这些值存储在另一个全局变量中(例如startTimestamp
),有效地开始您的时间测量。 - 当发生第二个事件时,例如一个引脚上的下降沿,在处理例程(例如引脚更改ISR)中,您读取当前定时器/外接器(TCNT)值并
timer1OvfCount
。现在,您有信号开始的时间戳(以startTimestamp
为单位),在另一个变量中具有信号结束的时间戳。这两个时间戳之间的差异正好是您所追求的脉冲的持续时间。
不过需要考虑两点:
- 当使用相位校正PWM模式时,定时器将在连续向上和向下计数之间交替。这使得查找自上次 TOV 中断以来通过的实际即时报价数量变得更加复杂。
- 一段代码先读取TCNT,然后读取
timer1OvfCount
,与TOV ISR之间可能存在竞争条件。这可以通过禁用中断,然后读取 TCNT,然后读取timer1OvfCount
,然后检查 TOV 中断标志来应对;如果设置了该标志,则存在挂起的、未处理的溢出中断 ->启用中断并重复。
但是,我很确定周围有几个库函数来维护软件扩展的计时器/计数器,这些计时器/计数器可以为您完成所有计时器处理。
700和2000的单位是什么?我猜是用的。您在问题中没有解释太多,但我确定您需要 25 毫秒持续时间的脉冲,其中 700 微塞时间可能是 0 度,2000 可能是 180 度,现在每个伺服的脉冲输入可以连接到 AVR 的任何 GPIO.并且这个 GPIO 提供 PWM 信号 Servo.so 我想你甚至可以用一个计时器控制这一切电机。使用这种代码:
假设您有一个计时器,每 50 次 USEC 生成一次 Inturrupt。现在,如果您想要 700 微塞用于电机1,800 微塞 电机 2,900 微塞用于电机 3 和 1000 微缩电用于电机 4,那么只需这样做:
#define CYCLE_PERIOD 500 // for 25 msec = 50 usec * 500
unsigned short motor1=14; // 700usec = 50x14
unsigned short motor2=16; // 800usec
unsigned short motor3=18; // 900usec
unsigned short motor4=20; // 1000usec
unsigned char motor1_high_flag=1;
unsigned char motor2_high_flag=1;
unsigned char motor3_high_flag=1;
unsigned char motor4_high_flag=1;
PA.0 = 1; // IO for motor1
PA.1 = 1; // IO for motor2
PA.2 = 1; // IO for motor3
PA.3 = 1; // IO for motor4
void timer_inturrupt_at_50usec()
{
motor1--;motor2--;motor3--;motor4--;
if(!motor1)
{
if(motor1_high_flag)
{
motor1_high_flag = 0;
PA.0 = 0;
motor1 = CYCLE_PERIOD - motor1;
}
if(!motor1_high_flag)
{
motor1_high_flag = 1;
PA.0 = 1;
motor1 = 14; // this one is dummy;if you want to change duty time update this in main
}
}
if(!motor2)
{
if(motor2_high_flag)
{
motor2_high_flag = 0;
PA.1 = 0;
motor2 = CYCLE_PERIOD - motor2;
}
if(!motor2_high_flag)
{
motor2_high_flag = 1;
PA.1 = 1;
motor2 = 16;
}
}
if(!motor3)
{
if(motor3_high_flag)
{
motor3_high_flag = 0;
PA.2 = 0;
motor3 = CYCLE_PERIOD - motor3;
}
if(!motor3_high_flag)
{
motor3_high_flag = 1;
PA.2 = 1;
motor3 = 18;
}
}
if(!motor4)
{
if(motor4_high_flag)
{
motor4_high_flag = 0;
PA.3 = 0;
motor4 = CYCLE_PERIOD - motor4;
}
if(!motor4_high_flag)
{
motor4_high_flag = 1;
PA.3 = 1;
motor4 = 19;
}
}
}
告诉我什么是ESC?