Generate pseudo PWM output on a non-PWM pin
This code is written for the ATMega32u4, but it can be easily ported to other microcontrollers. The ability to generate PWM output on non-PWM pins can come in handy, specially if you are working with devices where outputs are already hardwired to the microcontroller leaving you with limited freedom to pick pins. In my case, the hardwre is the Pololu Balboa robot. In order to use it in Assembly with the Pololu IR reflectance array, I needed to ping a non-PWM pin.
This code uses Timer/Counter 0 in Fast PWM mode with the OCR0B
match and TCNT0
overflow interrupts enabled. When the OCR0B
interrupt is called, the output on pin D6 (a non PWM pin), goes LOW. When the counter overflows, the output on D6 goes HIGH. By changing the value of the OCR0B
match, the duty cycle of the output can be changed. The error in the output timing depends on the length of your interrupt service routines. For very low error, you can remove saving/restoring the SREG
, taking care that the commands inside the ISRs do not affect the SREG
. The frequency of the output wave can be changed by changing clock prescalars for the Timer/Counter using CS02:0
bits in the TCCR0B
register.
The following screenshots show the output waveforms at different frequencies and duty cycles. Y axis scale is 2V/div.
50% duty ratio, ~ 7.8 kHz
6% duty ratio, ~ 7.8 kHz
6% duty ratio, ~ 61 Hz
50% duty ratio, ~ 61 Hz
; Stack registers
.equ SPH , 0x3E
.equ SPL , 0x3D
.equ RAMEND , 0x0AFF
.equ SREG , 0x3F
.equ DIDR2, 0x7D
; Timer/Counter0 registers
.equ DDRD , 0x0A
.equ DDRB , 0x04
.equ PORTD , 0x0B
.equ PORTB , 0x05
.equ DDRC , 0x07
.equ PORTC , 0x08
.equ TCCR0A , 0x24
.equ TCCR0B , 0x25
.equ OCR0A , 0x27
.equ OCR0B, 0x28
.equ TIMSK0, 0x6E
.equ D6 , 6
; TCCR0A bits
.equ COM0A1 , 7
.equ COM0A0 , 6
.equ COM0B1 , 5
.equ COM0B0 , 4
.equ WGM01 , 1
.equ WGM00 , 0
; TCCR0B bits
.equ WGM02 , 3
.equ CS02 , 2
.equ CS01 , 1
.equ CS00 , 0
.org 0x0000
rjmp reset; Power on interrupt
.org 0x58
rjmp OCR0B_int
.org 0x5c
rjmp TCNT0_ovflow_int
.org 0xAC; The vector table would end here if all interrupts are used
reset:
ldi r16 , hi8(RAMEND)
out SPH , r16
ldi r16 , lo8(RAMEND)
out SPL , r16
rcall pwmsetup
;rcall adcsetup
;rcall analogoutsetup
sei
main:
rjmp main
pwmsetup:
; Setup the 8-bit Timer/Counter 0 to operate in Fast PWM mode
ldi r16, 0b00000101
sts TIMSK0, r16
ldi r16, (0<<COM0B1)|(0<<COM0B0)|(0<<COM0A1)|(0<<COM0A0)|(1<<WGM01)|(1<<WGM00)
out TCCR0A ,r16
ldi r16 , (0<<WGM02)|(1<<CS02)|(0<<CS01)|(1<<CS00)
out TCCR0B , r16
ldi r16, 0x20 ; width of the ping
out OCR0B, r16
ret
OCR0B_int:
; For these two interrupts, I could afford to sacrifice the memory registers
; exclusively for saving and restoring SREG. They must not be used elsewhere
; in the code. If registers cannot be exclusive, they must be pushed to the stack
; before anything else happens in the ISR.
in r25, SREG ; save SREG
ldi r17, 0b00000000
ldi r19, 0b01000000
out DDRD, r19
out PORTD, r17
out SREG , r25 ; restore SREG
reti
TCNT0_ovflow_int:
in r23, SREG ; save SREG
ldi r18, 0b01000000
out DDRD, r18
out PORTD, r18
out SREG , r23 ; restore SREG
reti
Acknowledgement: I learned the basics of microcontroller programming in assembly as a Teaching Assistant for Yale’s Mechatronics Course (MENG 390). This code is based on code skeletons originally developed by the course instructor.