使用ATMEGA128产生任意PWM信号

generate arbitrary PWM signal using ATMEGA128

我非常熟悉 Atmega128 及其系列微控制器中的 PWM 生成。我一直在使用预分频器和其他寄存器来生成频率。但我必须生成 20KHz pwm 信号。我试过了,但无法获得所需的输出。谁能建议我或帮助我怎么做?

据我所知,在atmega128中,1条指令需要1个周期。使用 16MHz crystal,1 条指令在 1/16M 秒内完成。 我尝试生成占空比为 25us 的 20Khz 信号(50us)。但是我在示波器中得到了不同的频率(277.78 Hz),远小于 20KHz 我的计算是 16MH = 20000Hz * 800。 对于 0-399 计数,我将端口设置为高电平并且 399-799 计数,我把端口设置低了。

void frequency(void){   // 20kHz Frequency  
    if (cnt1 <= 399){
        PORTB |= (1<<7);
    } else {
        PORTB &= ~(1<<7);
    }
    cnt1++;
    if (cnt1 >= 800)    cnt1 = 0;
}

在 C 中使用计数器来实现 PWM 或任何时间关键的东西并不是一个好主意。尽管 C 将你的代码转换为特定的机器代码,但你真的不知道这需要多少时间。 您的代码 不会 转换为:

make port B high 400 times (PORTB |= (1<<7);)
make port B low 400 times (PORTB &= ~(1<<7);)

,而是像这样的东西(简化,human-readable):

load variable cnt1 to memA;
load 399 to memB
compare mem A to memB
put result to memC
if memC eq "somthing indicating <=" do PORTB |= (1<<7);
if memC something else do PORTB &= ~(1<<7);
load cnt1 to memD and increment;
write memD to cnt1;
load 800 to memE
load cnt1 to memF
compare memF to memE
put result to memG
if memG eq "somthing indicating <=" do memF = 0, write memF to cnt1;
if memG something else go to start;

如果您从 "C" 的角度来看,您至少需要 :

 1. comare cnt1-399
 2. if ok - do / else
 3. port high / port low
 4. add one to cnt1
 5. compare cnt1 and 800

这取决于你的编译器在优化所有加载和写入方面有多好(通常很好)。 如果您真的了解您的编译器并且不使用太多优化(通常很难遵循)或通过在汇编程序中编写代码,您可以控制延迟的时间。但是你将不得不使用类似于我对机器代码的解释的逻辑(汇编程序接近 human-readable 机器代码)。

我认为适合您的解决方案是定时器中断here.

有一个很好的 atmega128 教程

还有你是什么意思:

I tried to generate 20Khz signal (50 us)with 25us duty cycle.

你是说占空比为 50% 的 20kHz 信号吗?那么低 25us,高 25us?

如果是这种情况,您可以使用一个定时器中断和一个(二进制)计数器来实现。 正是您可以在提供的 link 中阅读的“8 位定时器示例”。

我无法访问 128,但已验证其 16 位定时器 1 与 328 和 32U4 中的相似,因此以下应该稍作修改即可(主要症结可能是查找什么引脚溢出寄存器绑定到):

#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 PB1 to output mode

#if defined(__AVR_ATmega32U4__)
        DDRB |= _BV(5);
#else
        DDRB |= _BV(1);
#endif
    }

    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();
    CTC1::set_freq(20e3);
}

void loop()
{
    // do whatever
    _delay_ms(1);
}

int main()
{
    setup();
    for (;;)
        loop();
}

我在我的示波器上进行了测试,并在 16MHz 的 328p 运行 上精确测量了 20kHz。如果 20kHz 是您唯一需要的频率,那么您可以大大简化它。将代码转换为使用其中一个 8 位定时器也很简单,尽管我还没有验证是否可以用这些定时器准确地达到 20kHz。