2012年8月30日木曜日

DCモータのPWM制御

最近のモータ制御では主流となっているPWM(Pulse Width Modulation)での制御をしてみた。

目的は
1.モータをPWMでドライブする。
2.可変抵抗(ボリューム)で回転数が変えられる。
3.正転、逆転が可能。
の3つ。
大昔の授業でマイコン制御のサンプルとして持ってきてもらったので良い実験材料かと。

で、まあ必要なのがボリュームの位置を読むAD変換と、
出力用のPWMという訳で。

PWM自体はやったことあったので2時間あれば組みきれると思ったら大間違い。
原因はプログラム特有の問題(オーバーフロー)だったりモータ特有の問題(突入電流など)だったり。


まず、手持ちでAD変換ができるチップがATMEGA88Pだったのでこれを使用。
データシートを見ると11番(PD5)と12番(PD6)がPWMの出力端子で、
23〜28番(PC0〜PC5)がAD変換用に使える。

PWM用の端子もたくさんあるみたいだけど今回はOC0xしか使わないので。

モータードライブにはTOSHIBAのTA8428K(S)を使用。
なければ自作でフルブリッジドライバを作るほかない。

ドライバはIN1がHIGH、IN2がLOWで正転、

IN1がLOW、IN2がHIGHで逆転、
両方LOWで開放、
両方HIGHでブレーキだったような気がする。
詳しくはデータシートを。


可変抵抗は今回特に何でもいいので適当。


プログラムリストは続きから。


今回、最も詰まった注意点を先に。
まず、volatile unsigned long でvalue を宣言しているけど、
途中で割り込み排除したのでvolatileは多分不要。

あと、これはint型ではオーバーフローしてしまう(最大値を超えてしまう)ので、必ずlong型に。
これに気づかずボリュームを上げてるのに、ある点で急に回転数が低くなってしまった。


もうひとつはマイコンの電源をACアダプタから取っていて、
ACアダプタが3Aまで流せるんだけど、そこにモータつないでたのも失敗だった。

やっぱりドライバには別電源が必要だと感じた。
モータの突入時(通電時)には大電流が流れるので、PWM制御でモータを動かすときには、
秒間何回も突入が起きて動作が不安定になる。

もっとACアダプタの定格電流が大きければ問題ないかもしれないけど、
今回は3Aだったので突入電流によってアダプタが落ちていたかも。
これに一番苦労させられた。

モータの通電時にアダプタが落ちて、一瞬後に復旧するわけだけど、
このときも通電時なわけでまたアダプタが落ちる。

このせいでモータが動いたと思った瞬間止まって、また動いてを繰り返す。
今回はPWM制御が目的だったから、この反応がむしろ正常に見えてデューティに問題がないかなどのプログラム上の問題を探しに行ったりもした。

また、モータドライバで2V程度電圧が落ちるらしいので、モータ用には少なくとも5V程度はほしい。
定格は30Vなのでよくある箱型の9V電池を使用した。


なので電源とオーバーフローには注意。
とくにAVRのAD変換器の出力は10bit(1024通り)なので。


プログラムの大まかな流れは、
AD変換の出力が0〜1023なので、半分の511で区切り、
0〜511が逆転、512〜1023が正転となってる。
ただし、511近傍では信号を与えない停止状態となってる。
この近傍の広さがdefineにあるBRANGEの値。

あと今回は可変抵抗の出力を電圧として読む必要が無いので、
0〜1023のままで使ってる。


では、以下リスト。


#include<avr/io.h>
#include<util/delay.h>
#include<avr/interrupt.h>

#define BRANGE 20

void init_pwm(){
TCCR0A = 0x00;
TCCR0B = 0x00;
TCCR0A |= 1<<COM0A1 | 1<<COM0B1;
TCCR0A |= 1<<WGM01 | 1<<WGM00;
TCCR0B |= 1<<CS01;
TCNT0 = 0;
}

void init_adc(){
ADMUX = 0x00;
ADCSRA = 0x00;
ADMUX |= 1<<REFS0;
ADCSRA |= 1<<ADEN | 1<<ADPS2;
}

int main(void){
init_pwm();
init_adc();
volatile unsigned long value = 0;
DDRB = 0xff;
DDRC = 0xfe;
DDRD = 0xff;
PORTB = 0x00;
PORTC = 0x00;
PORTD = 0x00;
while(1){
ADCSRA |= 1<<ADSC;
while(ADCSRA & (1<<ADSC));
value = ADCW;
if(value > 0x1ff+BRANGE){
TCCR0A |= 1<<COM0A1;
TCCR0A &= ~(1<<COM0B1);
OCR0A = (int)(((value-(0x1ff+BRANGE))*0xff)/(0x1ff-BRANGE));
}else if(value <= 0x1ff-BRANGE){
TCCR0A &= ~(1<<COM0A1);
TCCR0A |= 1<<COM0B1;
OCR0B = 0xff-(int)((value*0xff)/(0x1ff-BRANGE));
}else{
TCCR0A &= ~(1<<COM0A1);
TCCR0A &= ~(1<<COM0B1);
}
}
}

0 件のコメント:

コメントを投稿