AVR5: Control Brightness with PWM

In this scenario, you will control the LED brightness using PWM. To compare results, the built-in LED will be set to 100% on while you adjust LED4's brightness.

Prerequisites
You need to book one of the AVR laboratory nodes and ensure the video stream is live.
Get familiar with the scenario: AVR1: Hello World on AVR.

Scenario
Create an application that toggles the built-in LED (GPIO 13) on permanently, and then uses a timer (Timer1, channel B) to control LED4 (GPIO 10). The LED4 should present a heartbeat pattern.

In this example, we will build the code from the bottom up.

Result
Observe the heartbeat on LED4 (GPIO 10) and the solid LED on GPIO 13 (LED1, built-in).

Start
Mind to use AVR GCC syntax (as in the instruction): node compilation facilities are preconfigured, and you do not need to build a Makefile; still, it is necessary to follow the exact AVR GCC syntax, e.g., in the case of .equ.

Step 1
Compose a template for your application that includes declarations for GPIO and a timer. We use Timer1 (16-bit) to generate PWM on GPIO 10 (channel B):

.equ DDRB,   0x04   ; Port B Data Direction Register (I/O)
.equ PORTB,  0x05   ; Port B Data Register (I/O)
.equ SPCR,   0x2C   ; SPI Control Register (I/O)
.equ MCUSR,  0x34   ; MCU Status Register (I/O)
.equ SPL,    0x3D   ; Stack Pointer Low (I/O)
.equ SPH,    0x3E   ; Stack Pointer High (I/O)
.equ WDTCSR, 0x60   ; Watchdog Timer Control Register (Memory)
.equ TCCR1A, 0x80   ; Timer1 Control Register A (Memory)
.equ TCCR1B, 0x81   ; Timer1 Control Register B (Memory)
.equ OCR1BL, 0x8A   ; Timer1 Output Compare Register B Low
.equ OCR1BH, 0x8B   ; Timer1 Output Compare Register B High
 
; Bit Constants
.equ PB2,    2      ; Pin 10
.equ PB5,    5      ; Pin 13
.equ COM1B1, 5
.equ WGM10,  0
.equ CS11,   1
 
.section .text
 
; ==========================================================
; CLASSICAL INTERRUPT VECTOR TABLE (104 Bytes)
; ==========================================================
.org 0x0000
    jmp RESET
 
RESET:
....

Step 2
Initialise stack (obligatory, we use ISR function calls) and configure GPIOs 10 and 13 as outputs and enable GPIO 13 connected LED on:

...
   ldi r16, hi8(RAMEND)
   out SPH, r16
   ldi r16, lo8(RAMEND)
   out SPL, r16
 
    ; Setup Pins using Atomic 'sbi' (Set Bit in I/O)
    sbi DDRB, PB5       ; Set Pin 13 as Output
    sbi DDRB, PB2       ; Set Pin 10 as Output
    cbi PORTB, PB5      ; Turn ON Pin 13
...

Step 3
Configure timer for PWM (Timer1, channel B → GPIO 10):

...
    ldi r16, (1 << COM1B1) | (1 << WGM10)
    sts TCCR1A, r16
    ldi r16, (1 << CS11)
    sts TCCR1B, r16
    ldi r16, 0
    sts OCR1BH, r16
    sts OCR1BL, r16
...

Step 4
Now, AFTER the loop section (you perhaps don't have it yet, so put a dummy one in the code, temporary, implement functions to delay and to blink. Delays based on timer tick counting, not a timer, because we use Timer1 for PWM generation. There are 3 functions:

  • Function pulse modifies Timer1, channel B comparator value, thus changing the duty cycle of the PWM signal. Change is done every 1 ms. The duty cycle varies linearly.
  • Function delay_1ms is argument-less and delays (block execution using a loop) for about 1ms.
  • Function delay_N_ms is just a wrap-arround over delay_1ms:
; void pulse(void)
pulse:
    ldi r18, 0
fade_in:
    sts OCR1BL, r18
    rcall delay_1ms
    inc r18
    brne fade_in
 
    ldi r18, 255
fade_out:
    sts OCR1BL, r18
    rcall delay_1ms
    dec r18
    brne fade_out
 
    sts OCR1BL, r18 
    ret
 
; void delay_N_ms(uint8_t ms)
; Expects argument 'ms' in r24
delay_N_ms:
    tst r24
    breq dn_done
dn_loop:
    rcall delay_1ms
    dec r24
    brne dn_loop
dn_done:
    ret
 
; void delay_1ms(void)
delay_1ms:
    ldi r21, 21
d1_loop:
    ldi r22, 250
d2_loop:
    dec r22
    brne d2_loop
    dec r21
    brne d1_loop
    ret
Remember that the delay_N_ms function operates on 8-bit arguments, so the maximum delay is 255ms. If you need a longer one, modify it so it works with 16-bit arguments (r25:r24)

Step 5
Set the main loop. Here you're on your own, but the hint for the algorithm to have a real heartbeat is as follows:

loop infinitely:
    // --- First beat (Lub) ---
    pulse()
    delay(200)   // Wait 200 milliseconds

    // --- Second beat (Dub) ---
    pulse()
    delay(750)   // Wait 750 milliseconds (250 + 250 + 250)

Result validation
The LED4 should be flashing at a set interval (heartbeat), while LED1 (built-in) should be on. Note that some irregularity may is observed due to the nature of video streaming over the network. If you cannot clearly observe via the video stream, increase the heart rate via either the main loop or internally in the pulse function, e.g., by calling delay_N_ms with an appropriate parameter (r24) instead of delay_1ms.

FAQ
When using the printed version of this manual, please refer to the latest online version for the most up-to-date list of FAQs.

It does not work at all: Did you compile and upload to the device? Those are separate steps: it is not enough to just compile, but you also need to “flash” the MCU. Also, check your video stream if it “ticks” - the time embedded into the video stream should change. Your code may be working OK, but the video stream can be frozen, so you cannot see it working properly!
Built-in LED is off: Mind it is driven by zero, not by one.

en/multiasm/exercisesbook/avr/sut/scenarios/avr5.txt · Last modified: 2026/05/04 13:27 by pczekalski
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0