使用 4 个 16 位定时器实现 400hz PWM
Using 4 16bit timers for 400hz PWM
我正在处理基于 arduino mega 的四轴飞行器并尝试为 4 个电机设置 PWM 频率 - 每个 400hz。
我找到了一个有趣的解决方案,其中 4 个 ATmega2560 16 位定时器用于控制 4 个 ESC使用 PWM,因此它可以达到 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 个 esc 将提供 200hz。所以所有 16 位定时器都忙于控制 4 个 ESC,但我仍然需要从接收器读取输入 PPM。为此,我至少需要一个 16 位定时器,但我现在没有了。
它仍然是一个 8 位定时器空闲位,它只能读取 0..255 个数字,而正常数字 escs 操作是 1000.. 2000 年等等。
那么,如果我对 pwm 和 ppm 读数使用相同的 16 位定时器会怎样?行得通吗?它会大大降低速度吗?
我让 arduino 与 Raspberry Pi 配对工作,它控制数据过滤、调试和其他东西,将 ppm 读数移动到 Raspberry 是否更好?
700 和 2000 的单位是什么?我想 usec.You 没有在您的问题中解释太多,但我发现您需要 25 毫秒持续时间的脉冲,其中 700 微秒的时间可能是 0 度,2000 秒可能现在是 180 度,每个伺服的脉冲输入可以与 AVR.and 的任何 GPIO 相连,这个 GPIO 为 Servo.so 提供 PWM 信号,我想你甚至可以只用一个 timer.With 来控制所有电机这种代码:
假设您有一个每 50 微秒产生一次中断的定时器。
现在如果你想要 700 usec 电机 1,800 usec 电机 2,900 usec 电机 3 & 1000 usec 电机 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是什么?
回答您的问题之一:
So what would happen if I'll use same 16bit timer for both pwm and ppm
reading? Would it work?
是的。当您的引脚更改中断触发时,您可以 读取 当前的 TCNT 值以了解自上次中断以来已经过了多长时间。这不会以任何方式干扰定时器的硬件 PWM 操作。
Would it decrease speed drastically?
没有。 PWM 由专用硬件完成,同时软件操作 运行 不会影响其速度,也不会影响您为相应定时器激活的任何 ISR。因此,您可以让定时器根据需要生成 PWM,并仍然使用它来 a) 从中读取当前计数器值和 b) 将输出比较 and/or 溢出 ISR 连接到它以创建软件扩展定时器.
根据您的评论进行编辑:
注意TCNT寄存器中的实际值是任何时刻的当前定时器(tick)计数,与PWM是否激活无关。此外,定时器溢出中断 (TOV) 可用于任何模式。这两个属性允许通过以下步骤为任意其他时间测量任务制作软件扩展计时器:
- 为您要使用的 timer/counter 安装并激活定时器溢出中断。在 ISR 中,您基本上只是增加一个(易失性!)全局变量(例如
timer1OvfCount
),它有效地计算定时器溢出,从而扩展实际定时器范围。当前的绝对滴答计数可以计算为 timer1OvfCount * topTimerValue + TCNTx
。
- 当事件发生时,例如一个引脚上的上升沿,在处理例程(例如引脚更改 ISR)中,您读取当前 timer/couter (TCNT) 值 和
timer1OvfCount
并将这些值存储在另一个全局变量中(例如 startTimestamp
),有效地开始您的时间测量。
- 当第二个事件发生时,例如一个引脚上的下降沿,在处理例程(例如引脚更改 ISR)中,您读取当前 timer/couter (TCNT) 值 和
timer1OvfCount
。现在您在 startTimestamp
中有了信号开始的时间戳,在另一个变量中有了信号结束的时间戳。这两个时间戳之间的差异正是您所追求的脉冲持续时间。
不过有两点需要考虑:
- 当使用相位校正 PWM 模式时,定时器将在连续向上和向下计数之间交替。这使得查找自上次 TOV 中断以来经过的实际滴答数有点复杂。
- 一段代码首先读取 TCNT 然后读取
timer1OvfCount
与 TOV ISR 之间可能存在竞争条件。这可以通过禁用中断来解决,然后读取 TCNT,然后读取 timer1OvfCount
,然后检查 TOV 中断标志;如果设置了标志,则有一个未处理的、未处理的溢出中断 -> 启用中断并重复。
但是,我很确定有一些库函数可以维护软件扩展的 timer/counters,它们可以为您处理所有计时器。
我正在处理基于 arduino mega 的四轴飞行器并尝试为 4 个电机设置 PWM 频率 - 每个 400hz。
我找到了一个有趣的解决方案,其中 4 个 ATmega2560 16 位定时器用于控制 4 个 ESC使用 PWM,因此它可以达到 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 个 esc 将提供 200hz。所以所有 16 位定时器都忙于控制 4 个 ESC,但我仍然需要从接收器读取输入 PPM。为此,我至少需要一个 16 位定时器,但我现在没有了。
它仍然是一个 8 位定时器空闲位,它只能读取 0..255 个数字,而正常数字 escs 操作是 1000.. 2000 年等等。
那么,如果我对 pwm 和 ppm 读数使用相同的 16 位定时器会怎样?行得通吗?它会大大降低速度吗?
我让 arduino 与 Raspberry Pi 配对工作,它控制数据过滤、调试和其他东西,将 ppm 读数移动到 Raspberry 是否更好?
700 和 2000 的单位是什么?我想 usec.You 没有在您的问题中解释太多,但我发现您需要 25 毫秒持续时间的脉冲,其中 700 微秒的时间可能是 0 度,2000 秒可能现在是 180 度,每个伺服的脉冲输入可以与 AVR.and 的任何 GPIO 相连,这个 GPIO 为 Servo.so 提供 PWM 信号,我想你甚至可以只用一个 timer.With 来控制所有电机这种代码:
假设您有一个每 50 微秒产生一次中断的定时器。 现在如果你想要 700 usec 电机 1,800 usec 电机 2,900 usec 电机 3 & 1000 usec 电机 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是什么?
回答您的问题之一:
So what would happen if I'll use same 16bit timer for both pwm and ppm reading? Would it work?
是的。当您的引脚更改中断触发时,您可以 读取 当前的 TCNT 值以了解自上次中断以来已经过了多长时间。这不会以任何方式干扰定时器的硬件 PWM 操作。
Would it decrease speed drastically?
没有。 PWM 由专用硬件完成,同时软件操作 运行 不会影响其速度,也不会影响您为相应定时器激活的任何 ISR。因此,您可以让定时器根据需要生成 PWM,并仍然使用它来 a) 从中读取当前计数器值和 b) 将输出比较 and/or 溢出 ISR 连接到它以创建软件扩展定时器.
根据您的评论进行编辑:
注意TCNT寄存器中的实际值是任何时刻的当前定时器(tick)计数,与PWM是否激活无关。此外,定时器溢出中断 (TOV) 可用于任何模式。这两个属性允许通过以下步骤为任意其他时间测量任务制作软件扩展计时器:
- 为您要使用的 timer/counter 安装并激活定时器溢出中断。在 ISR 中,您基本上只是增加一个(易失性!)全局变量(例如
timer1OvfCount
),它有效地计算定时器溢出,从而扩展实际定时器范围。当前的绝对滴答计数可以计算为timer1OvfCount * topTimerValue + TCNTx
。 - 当事件发生时,例如一个引脚上的上升沿,在处理例程(例如引脚更改 ISR)中,您读取当前 timer/couter (TCNT) 值 和
timer1OvfCount
并将这些值存储在另一个全局变量中(例如startTimestamp
),有效地开始您的时间测量。 - 当第二个事件发生时,例如一个引脚上的下降沿,在处理例程(例如引脚更改 ISR)中,您读取当前 timer/couter (TCNT) 值 和
timer1OvfCount
。现在您在startTimestamp
中有了信号开始的时间戳,在另一个变量中有了信号结束的时间戳。这两个时间戳之间的差异正是您所追求的脉冲持续时间。
不过有两点需要考虑:
- 当使用相位校正 PWM 模式时,定时器将在连续向上和向下计数之间交替。这使得查找自上次 TOV 中断以来经过的实际滴答数有点复杂。
- 一段代码首先读取 TCNT 然后读取
timer1OvfCount
与 TOV ISR 之间可能存在竞争条件。这可以通过禁用中断来解决,然后读取 TCNT,然后读取timer1OvfCount
,然后检查 TOV 中断标志;如果设置了标志,则有一个未处理的、未处理的溢出中断 -> 启用中断并重复。
但是,我很确定有一些库函数可以维护软件扩展的 timer/counters,它们可以为您处理所有计时器。