Full Version : 8 Channel PWM for AVRs (ASM)
avr >>MOTORS SERVOS & PWM >>8 Channel PWM for AVRs (ASM)


AVR_Admin- 04-28-2006
8 Channel Proportionally Spaced PWM
For AVR Micro controllers by Dick Cappel

It doesn't take much in the way of hardware to test this code.


The assembly code given here was written to see what it would take to make an AT90S1200 generate 8 channels of proper PWM. In this case, by proper, I mean with the maximum high frequency content consistent with the needed duty cycle and give clock. Take a look at the scope shots below and notice that when the data value is =$02, there pulse frequency is twice that which occurs when the data value is =$01. The significance of this is that the percent of ripple voltage out of the low pass filter stays much more constant as the data values change than they would with a conventional PWM that merely varies the width of a single pulse.

When running with a 4 MHz clock, this code provides 8 channels of 8 bit resolution with a 60 Hz minimum frequency (which occur at data values of $01 and $FF. By adjusting the interrupt timer reload value, the minimum frequency can be taken to 100 Hz, but at the cost of time available for other tasks. Of course, changing the clock oscillator frequency is another way to increase the minimum frequency.

The code was originally written for and tested on an AT90S1200A, I also merged it with a monitor program and tested it on an AT90S2313. Thus, it can be adapted to a variety of AVR chips, including those with really limited resources such as the ATTiny12 easily.

The code

This code is an example program with the routines necessary to make the PWM machine work, but it only drives values that are preprogrammed in as constants at assembly time. Adding an interface so the PWM values can be adjusted in real time is left as an exercize for the student.

Performance

Step size is very good. The largest one I found was about 1/2 lsb. I measured this with a single pole RC filter and a 3-1/2 digit DVM, so I suspect that the apparent 1/2 lsb step error is actually much smaller and the error is really the result of the resolution of the measurement. PWM dacs can be very, very linear. What was surprising was to find some cross talk from another channel. All these signals were measured in PORTB bit 4, and while changing the values of bits 0,1,2,3,5,and 6 had not apparent affect on bit 4's voltage, changing bit 7 from $00 to $FF caused about a 1.5 lsb error in bit 4's voltage. Maybe this is a case for buffering PWM outputs in critical situations (then again it was critical, maybe a real DAC would be better).

Link to Site: http://cappels.org/dproj/8ch%20pwm/8chasm.htm


AVR_Admin- 04-28-2006
CODE


;******************************************************
.include "1200def.inc"
;******************************************************
;
;8 channel PWM routine. All the actiion is during the interrupt time (nothing happens
;during non-interrupt time except waiting for interrupts). There is only
;one suroutine, and its called before the interrupt, so this only uses one
;level of the stack (and an AT90S1200 doesn''t have much stack space to start with).
;
;This can be made faster by changing the interrupt timer reload value. $ED looks safe.
;It can also go faster (have a higher minimum pulse fequency) if there are fewer
;PWM channels or less resolution on the pulse width
;.

.def tempcounter = r1 ;variable used to reverse order of bits
.def pwmvalue   = r2 ;variable used to reverse order of bits  
.def pwmvalue0  = r3 ;duty cycle
.def pwmvalue1   = r4 ;duty cycle
.def pwmvalue2   = r5 ;duty cycle
.def pwmvalue3   = r6 ;duty cycle
.def pwmvalue4  = r7 ;duty cycle
.def pwmvalue5   = r8 ;duty cycle
.def pwmvalue6   = r9 ;duty cycle
.def pwmvalue7   = r10 ;duty cycle
.def oldticks  = r11 ;variable used to decide when to make a pulse
.def ticks   = r12 ;relative interrupt number mod 8

.def bout = r19 ;temporary buffer for output port b
.def temp  = r20 ;scratch register for non-interrupt times
.def itemp = r21 ;scratch register for interrupt times
                              ;having separate scratch registers saves stack space and time.
.ORG $0000
rjmp  start
.ORG $0002
rjmp  timerservice
rjmp  start
rjmp  start
rjmp  start

       
start:

ldi  temp, $01; use temp to initaize prescaler to divide by 1
out  TCCR0, temp
ldi  temp,$E0; use temp to initialize counter
out  TCNT0,temp
ldi  temp, $02; use temp to initialize TIMSK
out  TIMSK,temp

ldi  temp, $FF;set port B as output port
out  DDRB,temp
 
ldi  temp,$02     ;Enable timer interrupt
out  TIMSK,temp

ldi temp,$00
mov  ticks,temp


ldi  temp,$00; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue0, pwmvalue


ldi  temp,$01; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue1, pwmvalue


ldi  temp,$02; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue2, pwmvalue
 
ldi  temp,$0A; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue3, pwmvalue

ldi  temp,$80; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue4,pwmvalue

ldi  temp,$A0; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue5, pwmvalue

ldi  temp,$E0; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue6, pwmvalue

ldi  temp,$FF; test value for pwm value
mov pwmvalue,temp
rcall reversepwmvalue
mov pwmvalue7, pwmvalue

sei ; enable interrupts



loop:   ;wait here for interrupt

rjmp loop


;REVERSE BIT ORDER OR PWMVALUE. Midifies pwmvalue, uses temp
reversepwmvalue:  ; reverse bit order in pwmvale.
ldi  temp,$08 ; 8 times through loop because there are 8 bits
mov tempcounter,temp
rotateagain:
rol pwmvalue  
ror temp
dec tempcounter

brne rotateagain
mov pwmvalue,temp
ret




timerservice:
ldi itemp,$b4;set 75 clocks until next interrupt
out  TCNT0,itemp
out portb,bout;transfer portb buffer to port b
andi bout,$00;preset all PWM bits to zero
mov oldticks,ticks;copy interrupt counter to temp
inc ticks ;increment interrupt timer
eor oldticks,ticks;do X-OR and AND to find bits that changed to 1 (result in temp)
and oldticks,ticks;do AND to find bits that just changes to a 1



; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 0
mov itemp,oldticks
and itemp,pwmvalue0;AND to find if ones in data correspond to this interrupt count change
breq dontset0 ; if ones present, set lsb in portb  buffer
ori bout,$01
dontset0:


; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 1
mov itemp,oldticks
and itemp,pwmvalue1;AND to find if ones in data correspond to this interrupt count change
breq dontset1 ; if ones present, set lsb in portb  buffer
ori bout,$02
dontset1:



; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 2
mov itemp,oldticks
and itemp,pwmvalue2;AND to find if ones in data correspond to this interrupt count change
breq dontset2 ; if ones present, set lsb in portb  buffer
ori bout,$04
 
dontset2:


; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 3
mov itemp,oldticks
and itemp,pwmvalue3;AND to find if ones in data correspond to this interrupt count change
breq dontset3 ; if ones present, set lsb in portb  buffer
ori bout,$08
 
dontset3:

; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 3
mov itemp,oldticks
and itemp,pwmvalue4;AND to find if ones in data correspond to this interrupt count change
breq dontset4 ; if ones present, set lsb in portb  buffer
ori bout,$10
 
dontset4:


; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 3
mov itemp,oldticks
and itemp,pwmvalue5;AND to find if ones in data correspond to this interrupt count change
breq dontset5 ; if ones present, set lsb in portb  buffer
ori bout,$20
 
dontset5:


; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 3
mov itemp,oldticks
and itemp,pwmvalue6;AND to find if ones in data correspond to this interrupt count change
breq dontset6 ; if ones present, set lsb in portb  buffer
ori bout,$40
 
dontset6:


; OUTPUT PWM COMB WAVEFORM TO PORTB BIT 3
mov itemp,oldticks
and itemp,pwmvalue7;AND to find if ones in data correspond to this interrupt count change
breq dontset7 ; if ones present, set lsb in portb  buffer
ori bout,$80
 
dontset7:

reti;finished with interrupt service




Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.