我想生成一个具有可变频率和固定占空比(50%)的PWM信号。频率应在0-25KHz之间变化。这适用于ATMEGA32U4微控制器,我使用Atmel Studio在C语言中编写。我确实阅读过数据表,但我无法理解如何进行计算以及应该使用哪种模式。经过不同的教程后,我发现最好使用CTC模式。
如何选择我应该使用哪个预标量,因为频率是变量?我需要使用中断吗?有关如何设置这些定时器寄存器的任何帮助表示赞赏。
答案 0 :(得分:2)
32U4的定时器与我对你的问题进行测试的328P相同。我使用Timer 1提供最佳分辨率。该定时器可以在CTC模式下运行,通道A可以在比较匹配中与固定输出引脚绑定。这使得设置非常简单,不需要中断逻辑。只需写入OCR1A 即可控制频率(此寄存器为双缓冲,因此频率变化应无毛刺) *。
在CTC模式下,定时器1的输出频率为:
f n x = f_cpu / (2 * n * (1 + x))
其中n是预标度值,x是溢出比较寄存器。探索16MHz时钟的可能频率范围给出:
| N | f-min | f-max | r-min | r-max | x-100 | x-25k |
+-----+--------+-----------+-----------+-----------+-------+-------+
| 1 | 122.1 | 8,000,000 | 4,000,000 | 0.0019 | n/a | 319 |
| 8 | 15.3 | 1,000,000 | 500,000 | 0.00023 | 9,999 | 39 |
| 64 | 1.91 | 125,000 | 62,500 | 0.000029 | 1,249 | 4 |
| 256 | 0.49 | 31,250 | 15,625 | 0.0000073 | 311 | n/a |
|1024 | 0.12 | 7,812 | 3,906 | 0.0000018 | 77 | n/a |
其中N是预标度设置,f-min& f-max可达到的最小和最大频率r-min& r-max最小和最大频率分辨率,最后是x-100& x-25k分别为100Hz和25kHz输出的OCR1A所需的值。
对于一个完整的工作示例,这里有一个程序,以2秒的步长循环通过频率1Hz,2,5,10 ... 500kHz,足以观察范围内的工作:
#include <avr/io.h>
#include <util/delay.h>
struct CTC1
{
static void setup()
{
// CTC mode with TOP-OCR1A
TCCR1A = 0;
TCCR1B = _BV(WGM12);
// toggle channel A on compare match
TCCR1A = (TCCR1A & ~(_BV(COM1A1) | _BV(COM1A0))) | _BV(COM1A0);
// set channel A bound pin to output mode
DDRB |= _BV(1); // PB1 on 328p, use _BV(5) for PB5 on 32U4
}
static void set_freq(float f)
{
static const float f1 = min_freq(1), f8 = min_freq(8), f64 = min_freq(64), f256 = min_freq(256);
uint16_t n;
if (f >= f1) n = 1;
else if (f >= f8) n = 8;
else if (f >= f64) n = 64;
else if (f >= f256) n = 256;
else n = 1024;
prescale(n);
OCR1A = static_cast<uint16_t>(round(F_CPU / (2 * n * f) - 1));
}
static void prescale(uint16_t n)
{
uint8_t bits = 0;
switch (n)
{
case 1: bits = _BV(CS10); break;
case 8: bits = _BV(CS11); break;
case 64: bits = _BV(CS11) | _BV(CS10); break;
case 256: bits = _BV(CS12); break;
case 1024: bits = _BV(CS12) | _BV(CS10); break;
default: bits = 0;
}
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11) | _BV(CS10))) | bits;
}
static inline float min_freq(uint16_t n)
{
return ceil(F_CPU / (2 * n * 65536));
}
};
void setup()
{
CTC1::setup();
}
void loop()
{
for (uint8_t x = 0; x < 6; ++x)
for (uint8_t y = 0; y < 3; ++y)
{
float k = y > 0 ? (y > 1 ? 5 : 2) : 1;
CTC1::set_freq(k * pow(10, x));
_delay_ms(2000);
}
}
int main()
{
setup();
for (;;)
loop();
}
在PB1(Arduino Uno上的数字引脚9)上可以观察到信号。请注意,在32U4频道上,A绑定到PB5。
正如Aleksander Z先生所说,OCR1A寄存器在CTC模式下不是双缓冲的。当切换频率时,这可能导致严重的故障,例如:
根据应用的不同,这可以通过忙循环轻松解决(尽管这可能不适用于非常高的频率,或者可能在极低频率下导致不可接受的延迟):
while (TCNT1 > x)
;
OCR1A = x;
产:
答案 1 :(得分:0)
我根本不使用ATMEGA32U4我更喜欢AT32UC3所以很抱歉没有确切的代码...但是
为什么不把定时器设置为所需频率的两倍并在其内部切换引脚?因为你的职责总是50%应该有效。频率更新可以在每个偶数或每n-th
个ISR计数器/定时器呼叫...
类似的东西:
const int n=10; // must be even to ensure 50% duty
volatile int ix=0;
volatile int frequency=10000;
isr_timercounter()
{
// clear IRQ
// toggle pin
ix++; if (ix>=n)
{
ix=0;
// set timer/counter to 2*frequency
}
}
void main()
{
// init timer/counter
for (;;)
{
//do your stuff
frequency = ???;
}
}