如何正确设置ATmega328P的Timer1 16位P&F PWM的频率与占空比
Hey there! Let's break this down clearly—no jargon overload, promise. I totally get how confusing datasheets and online resources can be when you're starting out, so let's walk through the best approach step by step.
Key Background (Simplified)
To generate high-frequency square waves with adjustable duty cycles, we'll use Timer1 on the ATmega328P. Here's why:
- Timer1 is 16-bit, which lets us set a custom "count-to" value (called TOP) to hit your target frequencies exactly.
- We'll use Fast PWM Mode—this lets the hardware handle pin toggling automatically (no messy interrupts needed), which is efficient and reliable for high frequencies.
Step 1: Calculate Timer Parameters
Your ATmega328P runs at 8MHz, so each clock cycle is 0.125µs. We use this formula to find the TOP value for your target frequencies:Frequency = F_CPU / (Prescaler * (TOP + 1))
We'll use a prescaler of 1 (no clock division) to get the highest possible frequency resolution.
For ~111KHz:
8,000,000 / (1 * (TOP + 1)) = 111,111 (closest exact value to 111KHz)
→ TOP + 1 = 72 → TOP = 71
For ~120KHz:
8,000,000 / (1 * (TOP + 1)) ≈ 120,000
→ TOP + 1 ≈ 66.67 → TOP = 66 (gives ~119.4KHz, which is close enough for most use cases)
Step 2: Full Code Example
We'll use pin PB1 (Arduino D9, mapped to OC1A) as our output pin—this pin is tied directly to Timer1's hardware PWM, so it'll toggle automatically.
#include <avr/io.h> // Initialize PWM for 111KHz (change ICR1 to 66 for ~120KHz) void init_pwm() { // Set PB1 (OC1A) as output pin DDRB |= (1 << PB1); // Configure Timer1 for Fast PWM mode (ICR1 as TOP, non-inverting output) // COM1A1=1: Clear OC1A on match with OCR1A, set on TOP (non-inverting) // WGM11=1: Part of Fast PWM mode config TCCR1A |= (1 << COM1A1) | (1 << WGM11); // WGM13=1 + WGM12=1: Fast PWM with ICR1 as TOP // CS10=1: Prescaler = 1 (use full 8MHz clock) TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS10); // Set TOP value for 111KHz (replace with 66 for ~120KHz) ICR1 = 71; // Set initial duty cycle to 50% (adjust this value to change duty cycle) // Duty cycle formula: (OCR1A + 1) / (ICR1 + 1) * 100% OCR1A = 35; } int main(void) { init_pwm(); // Main loop: If you want to adjust duty cycle dynamically, add code here // Example: Read a potentiometer via ADC and update OCR1A while (1) { // Add your duty cycle adjustment logic here } }
Step 3: Critical Setup Notes
- Fuse Bit Configuration: The ATmega328P defaults to a 1MHz internal clock. You need to set the fuses to use the 8MHz internal clock:
- Set CKSEL[3:0] to
0010(internal 8MHz oscillator, no division) - If using Arduino tools, select "Internal 8MHz" under Tools > Clock and re-burn the bootloader.
- Set CKSEL[3:0] to
- Duty Cycle Adjustment: To change the duty cycle, update the
OCR1Aregister:OCR1A = 0: 0% duty cycle (pin stays low)OCR1A = ICR1: 100% duty cycle (pin stays high)- Values between 0 and ICR1 give proportional duty cycles (e.g.,
OCR1A = 18gives ~25% for 111KHz)
- Pin Selection: If you prefer using PB2 (Arduino D10, OC1B), adjust the code to use
COM1B1instead ofCOM1A1, andOCR1Binstead ofOCR1A.
Common Pitfalls to Check in Your Existing Code
- Forgetting to set the output pin as an output (
DDRxregister) - Using the wrong Timer mode (Fast PWM is essential here—other modes won't hit your target frequencies)
- Incorrect prescaler setting (using a prescaler higher than 1 will lower the maximum possible frequency)
- Not setting the fuse bits for 8MHz clock (your frequencies will be 8x lower if you're still on 1MHz!)
Hope this clears things up! If you share your existing code, we can spot any specific issues together.
内容的提问来源于stack exchange,提问作者SergeantSeven

