1

我正在尝试使用 Teensy 2.0 微控制器(基于 ATMEGA32U4 8 位 AVR 16 MHz)设置两个定时器中断程序,以独立控制两个伺服电机

经过多次尝试 - 我能够在端口 C 的引脚 7 上设置一个,但是

  1. 如何设置第二个 ISR 以独立于第一个进行初始化和调用?
  2. 我是否需要设置第二个计时器,如果需要,这样的代码会是什么样子?

这是设置代码:

int main(void)
{
    DDRE = 0xFF; 

    TCCR1A |= 1 << WGM12;       // Configure timer 1 for CTC mode 
    TCCR1B = (1<<WGM12) | (1<<CS11) ;

    OCR1A = 1000;   // initial            

    TIMSK1 |= 1 << OCIE1A;      // Output Compare A Match Interrupt Enable 
    sei();                      // enable interrupts 

    // ...code that sets pulseWidth based on app logic variable. 
    // Not showing as its not important
}


ISR(TIMER1_COMPA_vect)
{ 
    if (0 == pulseWidth)
    {
        return;
    }

    static uint8_t state = 0;
    int dutyTotal = 20*1000;    

    if (0 == state)
    {
        PORTC |= 0b10000000; 
        OCR1A = pulseWidth; 
        state = 1;
    }
    else if (1 == state)
    {
        PORTC &= 0b01111111; 
        OCR1A = dutyTotal - pulseWidth; 
        state = 0;
    }
}
4

2 回答 2

3

虽然很难在不了解您的应用的情况下给出明确的答案(例如,什么样的伺服/电机, - 我猜是 1-2ms pule 的 RC 型号?)有两种方法可以解决这个问题:

首先,在您的代码中,您似乎是通过切换 PC7 手动生成 PWM 信号。您可以通过增加状态数量来添加另一个输出 - 您需要比伺服器数量多一个来提供设置脉冲重复频率的间隙。当您需要驱动大量舵机时,这是一种常用技术,因为大多数 RC 舵机不关心脉冲相位或频率(在限制范围内),只关心脉冲宽度,因此您可以在之后生成一堆不同的脉冲其他在不同的输出上,而只使用这样的一个计时器(在一种伪代码状态图中):

State 0:
Turn on output 1
Set timer TOP to pulse duration 1.
Go to state 1:

State 1:
Turn off output 1
Turn on output 2
Set timer TOP to pulse duration 1.
Go to state 2:

State 2:
Turn off output 2
Set timer TOP to pulse duration 3.
Go to state 0:

“脉冲持续时间 3”设置 PRF(脉冲重复频率)。如果你想花哨,你可以将它设置为 1/f-pd1-pd2,以提供一个恒定的频率。

[“TOP”是AVR-speak,用于设置定时器的换行(溢出)率。见数据表。]

其次,如果您只使用两个伺服系统,还有一种更简单的方法 - 使用定时器的硬件 PWM 功能。AVR 定时器具有内置的 PWM 功能,可以为您进行引脚切换。mega32 上的 Timer1 有两个 PWM 输出引脚,它可以很好地用于您的两个伺服系统,然后您根本不需要(必然)需要中断处理程序。如果您直接使用 PWM 驱动电机(例如通过 H 桥),这也是正确的解决方案。

为此,您需要将定时器置于 PWM 模式并启用 OC1A 和 OC1B 输出引脚,例如

/*
 * Set fast PWM mode on OC1A and OC1B with ICR1 as TOP
 * (Mode 14)
 */
TCCR1A = (1 << WGM11) | (1 << COM1B1) | (1 << COM1A1);
TCCR1B = (3 << WGM12);

/*
 * Clock source internal, pre-scale by 8
 * (i.e. count rate = 2MHz for 16MHz crystal)
 */
TCCR1B |= (1 << CS11);

/*
 * Set counter TOP value to set pulse repetition frequency.
 * E.g. 50Hz (good for RC servos):
 * 2e6/50 = 40000. N.B. This must be less than 65535.
 * We count from t down to 0 so subtract 1 for true freq.
 */
ICR1 = 40000-1;

/* Enable OC1A and OC1B PWM output */
DDRB |= (1 << PB5) | (1 << PB6);

/* Uncomment to enable TIMER1_OVF_vect interrupts at 50Hz */
/* TIMSK1 = (1 << TOV1); */

/*
 * Set both servos to centre (1.5ms pulse).
 * Value for OCR1x is 2000 per ms then subtract one.
 */
OCR1A = 3000-1;
OCR1B = 3000-1;

免责声明 - 此代码片段可以编译,但我尚未在实际设备上检查它,因此您可能需要仔细检查寄存器值。请参阅http://www.atmel.com/Images/doc7766.pdf上的完整数据表

此外,您的代码中可能有一些拼写错误,TCC1A 中不存在位 WGM12(您实际上设置了位 3,即 FOC1A -“强制比较”,请参见数据表。)此外,您正在编写 DDRE 以启用输出在端口 E 上,但在端口 C 上切换引脚。

于 2013-10-18T22:08:02.603 回答
0

Halzephron,非常感谢您的回答。我没有足够高的声誉来标记您的答案,希望其他人会。

详情如下:

您能够使用单个 IRS 来控制多个伺服系统是绝对正确的 - 令人尴尬的是我没有想到这一点,但考虑到您更简单的解决方案的效果如何 - 我会使用它。

...此外,您正在编写 DDRE 以启用端口 E 上的输出,但切换端口 C 上的引脚。

谢谢,我注释掉了那行,它的工作原理是一样的 - (所以我根本不需要启用输出?)

TCC1A 中不存在位 WGM12(您实际上设置了位 3,即 FOC1A -“强制比较”,请参阅数据表。)

我也删除了它,但我的其余代码保持不变 - 它导致伺服系统移动速度更慢,扭矩和抖动更小,即使在到达所需位置之后也是如此。伺服发出一种奇怪的“摇晃”噪音(频率约为 10-20,我会说)并且它的手臂在颤抖,所以出于我不明白的原因 - 设置这个位似乎是必要的。

我怀疑每个电机的定时器非常不雅,所以我真的很喜欢你的第二种方法(内置定时器生成的 PWM),

...如果您直接使用 PWM 驱动电机(例如通过 H 桥),这也是正确的解决方案。

很好奇,为什么?即,使用这种方法与使用定时器生成的 PWM 有什么区别。

在您的代码中,我必须更改以下行以获得 50HZ,否则我之前获得 25HZ(使用范围验证)ICR1 = 20000-1; // 是 40000 - 1;

我注意到范围的另一件事是,我拥有的计时器生成的 PWM 代码 - 产生的“矩形”形状比您附加的 PWM 代码片段少。使用我的代码,信号下降到 0 大约需要 0.5 毫秒,而你的代码绝对是瞬时的(这很棒)。这解决了我一直在努力解决的另一个问题:我可以让模拟伺服系统与我的代码(IRS 生成的 PWM)一起正常工作,但是我尝试过的任何数字伺服系统 - 只是没有移动,就好像它坏了一样。我猜信号的形状对于数字伺服系统至关重要,我从来没有在任何地方读过这个。或者也许它的其他东西我不知道。

作为旁注,我花了很多时间在另一个奇怪的地方 - 我取消了 TIMSK1 = (1 << TOV1); 的注释。线,以为我总是需要它 - 但发生了什么,我的主函数会被冻结,在第一次调用 delay_ms(...) 时永远被阻塞,不确定它是什么 - 但保持它被注释掉会解除阻塞我的主循环(我在哪里读到使用 Teensy 的示例代码从 USB HID 获取伺服定位值)

再次,非常感谢您的帮助。

于 2013-10-20T12:25:11.600 回答