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
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.