为 AVR 生成可变频率 PWM 信号

ADG*_*GAN 2 avr pwm

我想生成一个具有可变频率和固定占空比(50%)的 PWM 信号。频率应在 0-25KHz 之间变化。这是针对 ATMEGA32U4 微控制器的,我使用 Atmel Studio 用 C 语言编写它。我确实阅读了数据表,但我无法理解如何进行计算以及应该使用哪种模式。在浏览了不同的教程后,我发现最好使用 CTC 模式。

由于频率是变量,如何选择应使用哪个预分频器?我需要使用中断吗?对于如何设置这些定时器寄存器的任何帮助,我们将不胜感激。

mar*_*sto 5

32U4 的计时器与我针对您的问题进行测试的 328P 相同。我使用了定时器 1,它提供了最佳分辨率。该定时器可以在 CTC 模式下运行,并且通道 A 可以在比较匹配切换时绑定到固定输出引脚。这使得设置极其简单并且不需要中断逻辑。只需写入 OCR1A 即可控制频率(该寄存器是双缓冲的,因此频率的更改应该没有毛刺) *。

在 CTC 模式下,定时器 1 的输出频率为:

f n x = f_cpu / (2 * n * (1 + x))
Run Code Online (Sandbox Code Playgroud)

其中 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 |
Run Code Online (Sandbox Code Playgroud)

其中 N 是预缩放设置,f-min 和 f-max 是可实现的最小和最大频率 r-min 和 r-max 是最小和最大频率分辨率,最后 x-100 和 x-25k 是 OCR1A 所需的值分别为100Hz和25kHz输出。

对于一个完整的工作示例,这里是一个程序,它以 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();
}
Run Code Online (Sandbox Code Playgroud)

该信号可在 PB1(Arduino Uno 上的数字引脚 9)上观察到。请注意,在 32U4 上,通道 A 绑定到 PB5。

正如 Aleksander Z. 善意评论的那样,OCR1A 寄存器在 CTC 模式下不是双缓冲的。当切换频率时,这可能会导致严重的故障,例如:

在此输入图像描述

根据应用程序,这可以通过忙循环轻松解决(尽管这对于非常高的频率可能效果不佳,或者可能在非常低的频率下导致不可接受的延迟):

while (TCNT1 > x)
    ;
OCR1A = x;
Run Code Online (Sandbox Code Playgroud)

生产:

在此输入图像描述

  • _只需写入 OCR1A 即可控制频率(该寄存器是双缓冲的,因此频率的更改应该无干扰)。_ 不,**OCR1A 在 CTC 模式下不是双缓冲的:** [数据表](http ://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf),第 162 页说:_注意:在计数器运行时将 TOP 更改为接近 BOTTOM 的值必须请小心操作,因为 CTC 模式不提供双缓冲。如果写入 OCR1A 的新值低于 TCNT1 的当前值,计数器将错过比较匹配。_ (2认同)