AVRのマルチチャネルソフトウェアPWM

PWMとは何か、どのように機能するかについては特に説明しませんが、情報はインターネットで簡単に見つけることができます。 一般的な概念のみを説明します。 PWMはパルス幅変調(英語のPWM-Pulse Width Modulation)です。名前とパルスから、パルスとその幅に関連するものがあることは既に明らかです。 一定周波数のパルスの幅(持続時間)を変更すると、たとえば、光源の明るさ、モーターシャフトの回転速度、または任意の発熱体の温度を制御できます。 通常、マイクロコントローラがこのような負荷を制御するのはPWMの助けを借りてです。 マイクロコントローラーにはハードウェアPWM実装がありますが、残念ながら、ハードウェアPWMチャネルの数は制限されています。たとえば、ATmega88には6つ、ATtiny2313に4つ、ATmega8に3つ、ATtiny13には2つしかありません。 AVRでは、PWMチャネルはタイマーとそのOCRxx比較レジスタを使用します。 タスクに応じて内容を変更し、タイマーのパラメーターを設定することにより、レジスタに関連付けられた状態を制御し、終了します-1または0に適用します。同じことをプログラムで構成し、コントローラーの出力を制御し、最も重要なことは、より多くのPWM-オンボードハードウェアよりもチャンネル数が多い。 実際には、チャネルの数は、マイクロコントローラのピンレッグの数によってのみ制限されます(少なくともMegaファミリまたはTinyファミリについて話す場合)。 判明したように、アルゴリズムは非常に単純ですが、それを理解して完全に理解するのに時間がかかりました。



このアルゴリズムは、元の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を使用してコンパイルされました。 ヒューズビットを忘れないでください(上記参照)。



All Articles