我正在尝试使用Teensy 2.0微控制器(基于ATMEGA32U4 8位AVR 16 MHz)设置两个定时器中断例程,以便独立控制两个伺服电机
经过多次试验 - 我能够在端口C的引脚7上设置一个,但是
以下是设置代码:
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;
}
}
答案 0 :(得分:1)
虽然很难在不了解更多应用程序的情况下给出明确的答案(例如,什么样的伺服/电机, - 我猜的模型RC类型有1-2ms pule?),有两种方法可以解决这个问题:
首先,在您的代码中,您似乎通过切换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,用于设置定时器的包装(溢出)速率。见数据表。 ]
其次,如果您只使用两个伺服器,则有一种更简单的方法 - 使用定时器的硬件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上的引脚。
答案 1 :(得分:0)
以下详细信息:
你能够使用单一的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&lt;&lt;&lt;&lt; TOV1);线,以为我总是需要它 - 但是发生了什么,我的主要功能会被冻结,在第一次调用delay_ms时永远被阻止(...)不确定它是什么 - 但是保持注释会解除阻塞我的主循环(我读到的地方)使用Teensy的示例代码从USB HID获取伺服positin值
再次,非常感谢您的帮助。