使用 Timer1 设置 Arduino Uno (ATMEGA328P) PWM
Setting up Arduino Uno (ATMEGA328P) PWM with Timer1
我想使用 16MHz Arduino Uno (ATMEGA328P) 设置自定义频率 (12Hz) 和占空比 (20%)。
AVR 计算器收益率:
ICR1 = 20833
OCR1A = 4167
我已经阅读了大量的论坛和 tuts,但出于某种原因我无法让它工作。
下面是我的代码:
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;
}
如果有人能帮忙,那就太好了!
谢谢,
迪伦
好的,我似乎找到了问题所在。我没有为模式 14 中的快速 PWM 正确设置寄存器(ATMEGA328P 有 15 种定时器 1 模式)。经过大量实验和进一步阅读,下面是可变频率和占空比的正确设置。 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 时,清除 Timer/Counter 控制寄存器很重要,因为它在执行 setup() 函数之前会在幕后进行一些设置。
变量"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;
Dylan144GT的回答非常好,但我想提醒的是,如果用最少的舍入误差完成方程式会更好。在他的代码中,"strobeFreq" 和 "strobeDutyCycle" 都是以频率表示的,方程式将 return 整数,如果您尝试插入小数值,这会导致一些问题,这是有道理的.让我举个例子:
完整的数据表“ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A”在第 16.9 节中为我们提供了一些非常有用的方程式。 CTC模式下的频率如下:
![image](https://latex.codecogs.com/svg.latex?f_%7BOC_%7BnA%7D%7D%3D%5Cfrac%7Bf_%7Bclk%5C_I/O%7D%7D%7B2%5Ccdot%20N%5Ccdot%20%281+OCRnA%29%7D)
我们可以修改这个等式,得到波形的周期而不是频率。为此,我们必须回忆起在第 16.9.2 节中,CTC 波形以 Toggle 模式(COMnA 模式 3)连接,因此我们必须删除 2,因为我们正在使用 Fast-PWM 模式。我们还必须用 ICRn 替换 OCRnA,因为在 Fast-PWM 模式 14 中,TOP 值由 ICRn 触发,而不是像 CTC 模式 4 中那样由 OCRnA 触发,如下所示:
![image](https://latex.codecogs.com/svg.latex?T_%7BOC_%7BnA%7D%7D%3D%5Cfrac%7BN%5Ccdot%20%281+OCRnA%29%7D%7Bf_%7Bclk%5C_I/O%7D%7D)
我们可以重新排列方程式,根据周期给出 ICRn 值:
![image](https://latex.codecogs.com/svg.latex?ICRn%3D%5Cfrac%7BT_%7BOC_%7BnA%7D%7D%5Ccdot%20f_%7Bclk%5C_I/O%7D%7D%7BN%7D%20-1)
如果我们将 N 替换为 1024,将 f_clk_I/O 替换为时钟频率 (16.000.000Hz = 16MHz),我们将观察到对于某些周期,例如 0.128s,结果将是
![image](https://latex.codecogs.com/svg.latex?1999%3D%5Cfrac%7B0.128%5C%20s%5Ccdot%2016%5C%20MHz%7D%7B1024%7D%20-1)
我们知道这是不可能的,因为我们不能在等式中输入小数浮点数。
如果我们将单位从秒更改为毫秒,反之亦然,我们可以将 ICRn 精度提高到个位数。要获得 6.25KHz 的频率,我们可以 select 以下值:
![image](https://latex.codecogs.com/svg.latex?%5Cfrac%7B160%5C%20ns%5Ccdot%2016%5C%20MHz%7D%7B256%7D%5Ccdot%20%5Cfrac%7B1%5C%20s%7D%7B10%5E6%5C%20ns%7D%20-1%3D10)
因为 "strobePeriod" 是 10,所以 "dutyCycle" 可以是 1 到 10 之间的数字。例如:dutyCycle 30% 是 3 的 OCR1A。
更正了更高精度值的代码:
// 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 中编码并且我需要高精度的应用程序。我希望这对某人有用。
我想使用 16MHz Arduino Uno (ATMEGA328P) 设置自定义频率 (12Hz) 和占空比 (20%)。
AVR 计算器收益率:
ICR1 = 20833
OCR1A = 4167
我已经阅读了大量的论坛和 tuts,但出于某种原因我无法让它工作。
下面是我的代码:
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;
}
如果有人能帮忙,那就太好了!
谢谢,
迪伦
好的,我似乎找到了问题所在。我没有为模式 14 中的快速 PWM 正确设置寄存器(ATMEGA328P 有 15 种定时器 1 模式)。经过大量实验和进一步阅读,下面是可变频率和占空比的正确设置。 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 时,清除 Timer/Counter 控制寄存器很重要,因为它在执行 setup() 函数之前会在幕后进行一些设置。
变量"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;
Dylan144GT的回答非常好,但我想提醒的是,如果用最少的舍入误差完成方程式会更好。在他的代码中,"strobeFreq" 和 "strobeDutyCycle" 都是以频率表示的,方程式将 return 整数,如果您尝试插入小数值,这会导致一些问题,这是有道理的.让我举个例子:
完整的数据表“ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A”在第 16.9 节中为我们提供了一些非常有用的方程式。 CTC模式下的频率如下:
我们可以修改这个等式,得到波形的周期而不是频率。为此,我们必须回忆起在第 16.9.2 节中,CTC 波形以 Toggle 模式(COMnA 模式 3)连接,因此我们必须删除 2,因为我们正在使用 Fast-PWM 模式。我们还必须用 ICRn 替换 OCRnA,因为在 Fast-PWM 模式 14 中,TOP 值由 ICRn 触发,而不是像 CTC 模式 4 中那样由 OCRnA 触发,如下所示:
我们可以重新排列方程式,根据周期给出 ICRn 值:
如果我们将 N 替换为 1024,将 f_clk_I/O 替换为时钟频率 (16.000.000Hz = 16MHz),我们将观察到对于某些周期,例如 0.128s,结果将是
我们知道这是不可能的,因为我们不能在等式中输入小数浮点数。 如果我们将单位从秒更改为毫秒,反之亦然,我们可以将 ICRn 精度提高到个位数。要获得 6.25KHz 的频率,我们可以 select 以下值:
因为 "strobePeriod" 是 10,所以 "dutyCycle" 可以是 1 到 10 之间的数字。例如:dutyCycle 30% 是 3 的 OCR1A。
更正了更高精度值的代码:
// 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 中编码并且我需要高精度的应用程序。我希望这对某人有用。