我想用16MHz Arduino Uno(ATMEGA328P)设置自定义频率(12Hz)和占空比(20%)。
AVR计算器产量:
ICR1 = 20833
OCR1A = 4167
我已经阅读了大量的论坛和论坛,但出于某种原因,我无法让它发挥作用。
以下是我的代码:
void setup()
{
// PB1 is now an output (Pin9 Arduino UNO)
DDRB |= (1 << DDB1);
// PB2 is now an output (Pin10 Arduino UNO)
DDRB |= (1 << DDB2);
// Set PWM frequency/top value
ICR1 = 20833;
// Set PWM duty cycle
OCR1A = 4167;
// Set inverting mode (start low, go high)
TCCR1A |= (1 << COM1A1);
TCCR1A |= (1 << COM1B1);
TCCR1A |= (1 << COM1A0);
TCCR1A |= (1 << COM1B0);
// Set fast PWM Mode
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << WGM13);
// Set prescaler to 64 and starts PWM
TCCR1B |= (1 << CS10);
TCCR1B |= (1 << CS11);
}
void loop() {
// Refresh PWM frequency
OCR1A = 4167;
}
如果有人可以提供帮助,那就太棒了!
谢谢,
迪伦
答案 0 :(得分:0)
好的,所以我似乎找到了这个问题。我没有为模式14中的快速PWM正确设置寄存器(ATMEGA328P有15个timer1模式)。经过大量的实验和进一步的阅读,下面是可变频率和占空比的正确设置。 ICR1表示TOP值(控制频率),OCR1A表示开关值(占空比)。
// ADJUSTABLE VARIABLES
// Strobe frequency
uint16_t timer1Prescaler = 64;
uint8_t strobeFreq = 20,
strobeDutyCycle = 20;
void setup
{
// Set PB1 to be an output (Pin9 Arduino UNO)
DDRB |= (1 << PB1);
// Clear Timer/Counter Control Registers
TCCR1A = 0;
TCCR1B = 0;
// Set non-inverting mode
TCCR1A |= (1 << COM1A1);
// Set fast PWM Mode 14
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << WGM13);
// Set prescaler to 64 and starts PWM
TCCR1B |= (1 << CS10);
TCCR1B |= (1 << CS11);
// Set PWM frequency/top value
ICR1 = (F_CPU / (timer1Prescaler*strobeFreq)) - 1;
OCR1A = ICR1 / (100 / strobeDutyCycle);
}
void loop()
{
// main loop code
}
注意:在使用Arduino IDE时清除T / C控制寄存器很重要,因为它在执行setup()函数之前会在幕后进行一些设置。
答案 1 :(得分:0)
在将变量“ strobeDutyCycle”设置为大于50的任何数字后,PWM停止工作。这是因为变量“ strobeDutyCycle”声明为uint8_t,这意味着,例如,如果我们有51,则100/51将等于1,因为unsigned int(“ uint8_t”)会舍去小数部分。因此,任何大于50的数字都将获得相同的数字,这将导致OC1A输出引脚上的输出为0。 解决该问题的方法是将变量“ strobeDutyCycle”声明为float,然后在除法完成后将其强制转换为uint16_t。
float strobeDutyCycle = 60;
uint16_t timer1Prescaler = 1024;
uint16_t strobeFreq= 2;
...
float pwmFrequency = (F_CPU / (timer1Prescaler*strobeFreq)) - 1;
float dutyCycleDivisor = 100 / strobeDutyCycle;
float pwmValueWithDutyCycle = pwmFrequency / dutyCycleDivisor;
ICR1 = (uint16_t)pwmFrequency;
OCR1A = (uint16_t)pwmValueWithDutyCycle;
答案 2 :(得分:0)
Dylan144GT 的答案非常好,但我想提醒一下,如果用最小的舍入误差完成方程,效果会更好。在他的代码中,“ strobeFreq”和“ strobeDutyCycle”全都以频率表示,方程式将返回整数,如果您尝试插入十进制值,这将引起一些问题,这将是有意义的。让我举一个例子:
完整的数据表“ ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A ”在第16.9节中提供了一些非常有用的公式。 CTC模式下的频率如下:
我们可以修改此方程式,以便为我们提供波形的周期而不是频率。为此,我们必须记得在16.9.2节中,CTC波形以切换模式(COMnA模式3)连接,因此我们必须删除2,因为我们正在使用快速PWM模式。我们还必须用ICRn替代OCRnA,因为在快速PWM模式14中,TOP值是由ICRn触发的,而不是像CTC模式4中那样由OCRnA触发的,就像这样:
我们可以重新计算等式,以根据周期为我们提供ICRn值:
如果用时钟频率(16.000.000Hz = 16MHz)用N代替1024,用f_clk_I / O代替N,我们将观察到在0.128s之类的时间段内,结果将是
这是不可能的,因为我们不能在公式中键入小数点。 如果将单位从秒更改为毫秒,反之亦然,则可以将ICRn精度提高到个位数。要获得6.25KHz的频率,我们可以选择以下值:
由于“ strobePeriod”为10,因此“ dutyCycle”可以为1到10之间的数字。例如:占空比30%的OCR1A为3。
更高精度的代码:
// ADJUSTABLE VARIABLES // Strobe frequency uint16_t timer1Prescaler = 64; /* 1, 8, 64, 256, 1024 */ uint8_t strobePeriod = 50, /* milliseconds */ strobeDutyCycle = 20; /* percent */ ... // Set PWM frequency/top value ICR1 = (F_CPU*strobePeriod / (timer1Prescaler*1000) ) - 1; /* equals 12499 */ OCR1A = ICR1 / (100 / strobeDutyCycle); /* equals 2499 */ } ...
我不得不写这篇文章,因为我通常在Assembly中编写代码,并且我需要高精度的应用程序。我希望这已被证明对某人有用。