使用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。
我非常熟悉 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。