AVR ATmega16汇编(非C)下PWM占空比修改无效问题排查
Let's break down why adjusting OCR1AL isn't changing your PWM duty cycle, and fix the issues in your code step by step:
1. Your Button Input Isn't Working (So the Duty Cycle Never Gets Updated)
The biggest blocker here is that your input port PORTA doesn’t have internal pull-up resistors enabled. You set DDRA to 0x00 (all inputs), but you didn’t set PORTA to 0xFF to activate the pull-ups. Without pull-ups, the pins are floating—their voltage is unpredictable, so your check cpi r23, 0b11111110 will almost never trigger. That means the code that modifies OCR1AL never runs at all!
Fix: Add this line right after setting DDRA:
ldi r16, 0xff out PORTA, r16
This enables pull-ups on all PORTA pins, so when your button (connected to PA0) is pressed, PA0 will read low (0), making PINA equal to 0b11111110 as expected.
2. You Misunderstood the Timer1 PWM Mode (OCR1A Isn’t the TOP Value Here)
You thought OCR1A acts as the TOP value, but that’s only true for specific PWM modes. Let’s look at your current timer configuration:
TCCR1A = 0xc1(binary11000001): SetsCOM1A1=1,COM1A0=1,WGM10=1,WGM11=0TCCR1B = 0x01(binary00000001): SetsWGM12=0,WGM13=0,CS10=1(no prescaling)
Combined, WGM13:0 = 0001, which activates 8-bit Phase Correct PWM mode. In this mode:
- The TOP value is fixed at
0x00FF(255) OCR1Ais the compare match value, which controls the duty cycle (not the TOP)
If you want OCR1A to be the TOP value (for custom PWM periods), you’d need to switch to Fast PWM mode with OCR1A as TOP (WGM mode 15). To do that, update your timer setup to:
ldi r20, 0b11001111 ; COM1A1=1, COM1A0=1, WGM11=1, WGM10=1 out TCCR1A, r20 ldi r20, 0b00011001 ; WGM13=1, WGM12=1, CS10=1 out TCCR1B, r20
But for your original goal of adjusting duty cycle from 20% to 70%, sticking with 8-bit Phase Correct PWM is perfectly fine—you just need to adjust how you calculate the duty cycle.
3. Your Compare Output Mode Affects Duty Cycle Calculation
With COM1A1=1 and COM1A0=1 in Phase Correct PWM:
- The OC1A pin (PD5) is set high when the timer counts up to
OCR1A - It’s cleared when the timer counts down to
OCR1A
This means the duty cycle is calculated as:Duty Cycle = (256 - OCR1A) / 256 * 100%
So your initial OCR1AL=0x33 (51) gives a duty cycle of (256-51)/256 ≈ 80%, not 20% as you intended. If you want higher duty cycles when OCR1A increases, switch to COM1A1=1 and COM1A0=0:
ldi r20, 0b10000001 ; COM1A1=1, COM1A0=0, WGM10=1 out TCCR1A, r20
Now the duty cycle is:Duty Cycle = (OCR1A + 1) / 256 * 100%
So OCR1AL=0x33 (51) gives (51+1)/256 ≈ 20%, and OCR1AL=0xB5 (181) gives (181+1)/256 ≈ 71%—which matches your goal of 20% to 70%.
Full Modified Code Snippet
Here’s how your code should look with all fixes applied:
.include "m16def.inc" .cseg PWM: ;input - enable pull-ups ldi r16, 0x00 out DDRA, r16 ldi r16, 0xff out PORTA, r16 ; Enable internal pull-ups on PORTA ;output ldi r16, 0xff out DDRD, r16 out PORTD, r16 ; Timer1 setup: 8-bit Phase Correct PWM, COM1A1=1, COM1A0=0 ldi r20, 0b10000001 out TCCR1A, r20 ldi r20, 0x01 out TCCR1B, r20 ; Initial duty cycle ~20% (OCR1A=51) ldi r20, 0x00 out OCR1AH, r20 ldi r20, 0x33 out OCR1AL , r20 in r22, TIFR andi r22, 0b00000100 out PORTD, r22 SW0: in r23, PINA cpi r23, 0b11111110 brne SW0 ; Update duty cycle to ~71% (51+130=182) ldi r16, 0x82 in r17, OCR1AL ADD r16,r17 out OCR1AL, r16 jmp SW0 ldi r16, 0x00 out PORTD, r16
Final Notes
- Double-check that you’re measuring the PWM signal on PD5 (OC1A), since that’s the pin tied to Timer1’s compare output.
- With a 4MHz clock and no prescaling, the 8-bit Phase Correct PWM will have a frequency of ~7.8kHz, which should be stable for most applications.
内容的提问来源于stack exchange,提问作者Giannis

