このアルゴリズムは、元のAppnote AVR136:低ジッタマルチチャネルソフトウェアPWMで詳しく説明されています。 ソフトウェア実装の原理は、PWMモードでタイマーをシミュレートすることです。 必要なパルス持続時間はそれぞれ変数(各チャネルに1つ(コードlev_ch1、lev_ch2、lev_ch3))によって設定され、またこれらの変数の「双子」が設定され、タイマーの特定の期間の値を格納します(コードbuf_lev_ch1、buf_lev_ch2に) buf_lev_ch3)。 8ビットタイマーはメインMK周波数で開始し、オーバーフロー割り込み、つまり256クロックサイクルごとに生成します。 これにより、割り込み処理手順の期間に制限が課されます。次の割り込みを見逃さないために、256クロックサイクル以内に維持する必要があります。 その結果、1つのフルPWM周期は256 * 256 = 65536クロックサイクルに等しくなります。 8ビットカウンター変数(この例ではカウンター)は、割り込みごとに1ずつ増加し、PWMサイクル内で位置インジケーターとして機能します。 これはすべて、1/256のPWMの分解能(最小ステップ)とƒ/(256 * 256)のパルス周波数を提供します。ここで、ƒはマイクロコントローラーのマスターオシレーターの周波数です。 マイクロコントローラのクロック周波数は非常に高くする必要があることに注意してください。 私の例では、ATtiny13は、外部ジェネレーターを使用せずに、9.6 MHzの最高周波数で動作します。 これにより、9600000 /65536≈146.5HzのPWM周期が得られ、ほとんどの場合、これで十分です。
Cコード、ATtiny13 MKのアイデアの実装例(出力PB0、PB1、PB2の3つのPWMチャンネル):
#define F_CPU 9600000 //fuse LOW=0x7a #include <avr/interrupt.h> #include <util/delay.h> uint8_t counter=0; uint8_t lev_ch1, lev_ch2, lev_ch3; uint8_t buf_lev_ch1, buf_lev_ch2, buf_lev_ch3; void delay_ms(uint8_t ms) // { while (ms) { _delay_ms(1); ms--; } } int main(void) { DDRB=0b00000111; // PortB 0,1,2 TIMSK0 = 0b00000010; // TCCR0B = 0b00000001; // , sei(); // lev_ch1=0; // lev_ch2=64; // lev_ch3=128; // while (1) // { for (uint8_t i=0;i<255;i++) { lev_ch1++; // lev_ch2++; // lev_ch3++; // delay_ms(50); // 50 } } } ISR (TIM0_OVF_vect) // { if (++counter==0) // { buf_lev_ch1=lev_ch1; // buf_lev_ch2=lev_ch2; buf_lev_ch3=lev_ch3; PORTB |=(1<<PB0)|(1<<PB1)|(1<<PB2); // 1 } if (counter==buf_lev_ch1) PORTB&=~(1<<PB1); // 0 if (counter==buf_lev_ch2) PORTB&=~(1<<PB0); // if (counter==buf_lev_ch3) PORTB&=~(1<<PB2); // . }
私はすべてが非常に明確であり、説明は冗長だと思います。 チャンネルの数が多いデュレーション値とそのバッファーの場合、配列を使用する方が良い場合がありますが、この例では、わかりやすくするためにこれを行いませんでした。
avr-gcc-4.7.1およびavr-libc-1.8.0でテスト済み。 ファームウェアファイルのコンパイルと取得:
avr-gcc -mmcu=attiny13 -Wall -Wstrict-prototypes -Os -mcall-prologues -std=c99 -o softPWM.obj softPWM.c
avr-objcopy -O ihex softPWM.obj softPWM.hex
適切に動作させるには、下位のヒューズビットを0x7a(周波数9.6 MHz)に設定する必要があります。 たとえば、avrdudeでは、これは次のように行われます。
avrdude -p t13 -c usbasp -U lfuse:w:0x7a:m
アセンブラーでの私の実装オプション。 プログラムは、以前のCコードとまったく同じ動作をします。
; include- .list .equ DDRB= 0x17 .equ PORTB= 0x18 .equ RAMEND= 0x009f .equ SPL= 0x3d .equ TCCR0B= 0x33 .equ TIMSK0= 0x39 .equ SREG= 0x3f ; , .def temp=R16 .def lev_ch1=R17 .def lev_ch2=R18 .def lev_ch3=R19 .def buf_lev_ch1=R13 .def buf_lev_ch2=R14 .def buf_lev_ch3=R15 .def counter=R20 .def delay0=R21 .def delay1=R22 .def delay2=R23 .cseg .org 0 ; : rjmp RESET ; Reset Handler rjmp EXT_INT0 ; IRQ0 Handler rjmp PIN_CHG_IRQ ; PCINT0 Handler rjmp TIM0_OVF ; Timer0 Overflow Handler rjmp EE_RDY ; EEPROM Ready Handler rjmp ANA_COMP ; Analog Comparator Handler rjmp TIM0_COMPA ; Timer0 CompareA Handler rjmp TIM0_COMPB ; Timer0 CompareB Handler rjmp WATCHDOG ; Watchdog Interrupt Handler rjmp ADC_IRQ ; ADC Conversion Handler ;RESET: EXT_INT0: PIN_CHG_IRQ: ;TIM0_OVF: EE_RDY: ANA_COMP: TIM0_COMPA: TIM0_COMPB: WATCHDOG: ADC_IRQ: reti RESET: ldi temp,0b00000111 ; PortB PB0, PB1 out DDRB,temp ; PB2 ldi temp,0 ; out PORTB,temp ; PortB 0 ldi temp,low(RAMEND) ; out SPL,temp ; ldi temp,0b00000001 ; . out TCCR0B,temp ; ldi temp,0b00000010 ; . out TIMSK0,temp ; sei ; start_pwm: ; inc lev_ch1 ; inc lev_ch2 ; inc lev_ch3 ; rcall delay ; rjmp start_pwm delay: ; ldi delay2,$01 ; ldi delay1,$77 ; ldi delay0,$00 ; $017700 - 50 loop: subi delay0,1 ; sbci delay1,0 ; sbci delay2,0 ; brcc loop ret TIM0_OVF: ; push temp ; in temp,SREG ; temp SREG push temp inc counter ; 0 cpi counter,0 ; 0, brne ch1_off ; mov buf_lev_ch1,lev_ch1 ; 0 mov buf_lev_ch2,lev_ch2 ; mov buf_lev_ch3,lev_ch3 ; ldi temp,0b00000111 ; out PORTB,temp ; ch1_off: ; cp counter,buf_lev_ch1 ; ? brne ch2_off ; , - cbi PORTB,0 ; ch2_off: ; cp counter,buf_lev_ch2 ; ? brne ch3_off ; , - cbi PORTB,1 ; ch3_off: ; cp counter,buf_lev_ch3 ; ? brne irq_end ; , - cbi PORTB,2 ; , irq_end: ; pop temp ; SREG temp out SREG,temp pop temp reti ;
avraまたはtavrasmを使用してコンパイルされました。 ヒューズビットを忘れないでください(上記参照)。