SSブログ

Mp3V5の32KHz時の負荷低減について [ds-DCCデコーダ]

 青春18きっぷを買ってみました。大学生の時以来ですね。でも、そういえば行くところすら決めていません。
 碓氷峠鉄道文化むらとか日帰りでいいかなあとか思います。東海道線が高崎まで直通で、朝6時に家を出ると10時に横川に着くようですし。
 さて、標題の話題ですが、
・Mp3V5で走行音を出すと若者にはモスキート音(16KHz)が聞こえる。
→理由は16KHzのパルスで音の波形を作っているため。そのパルスもアンプで強調されてしまうため。
・パルス周期を32KHzにすれば16KHzの音が小さくなるかもと思って、32KHzに変更した。
→マイコンの負荷が高くなって、DCCのパケットを取りこぼしやすくなったためか、走行時に暴走しやすくなった。との報告。(ごめんなさい。実車に乗せて確認してないです・・・。)
 で、どうすればマイコンの負荷低減ができて、DCCのパケットをもらうプロセスをたくさん回せるか(→暴走しなくなる)という話になっているかと思います。

 で、とりあえずVVVF部分の負荷を低減できるか実験してみました。すべて、VVVF_Sound.cppの話です。

 まずは、へのへのもへじ様やあやのすけ様からお聞きして、私もVVVF音部分の割り込み時どのくらい時間を占有しているかを確認してみるため、割り込み関数「void vvvf_int1()」の最初と最後に

PORTD ^= _BV(PD7);

を入れて、割り込み時にピンから信号を出力をさせて、Arduino7ピンからオシロで波形を見てみました。
 なお、上記をやるためにはスケッチの一番最初に、

pinMode(7, OUTPUT);

の、おまじないをしないと出力されません・・・。

 で、結果波形が以下です。まずはEL102bスケッチ(16KHzのもの)です。Lowの時が割り込み中です。
走行時
EL102b走行時.png

停止時
EL102b停止時.png

走行時は周期が62usぐらいで、割り込み時間は16.7usです。
停止時は周期が62usぐらいで、割り込み時間は0.8usです。

次に、EL102cスケッチ(32KHzのもの)です。16KHzごとにパルスDutyを変えるスケッチになります。
走行時
EL102c走行時.png

停止時
EL102c停止時.png

走行時は周期が31usぐらいで、割り込み時間は17.1us、0.8usを繰り返します。
停止時は周期が31usぐらいで、割り込み時間は1,3us、0.8usを繰り返します。

また、せっかくですから、割り込み関数内の各命令がどのくらい時間を食っているのかもついでに確認してみました。
中身ですが
(1)2回に1回だけ命令を実行するためのトグル関数・・・0.8us(7ピンの出力含む)
flg = !flg;
if(flg)
{
return;
}

(2)VVVF音を出すかどうかの条件分岐・・・0.5us
if (pwm_shift != 8)

(3)波形の計算・・・4.5us(ただしpwm_shiftの値で若干変わる)
a = ((pgm_read_byte_near(&WAV_DATA1 [pwm_state1 >> SHIFT_PWM]) * 3 + pgm_read_byte_near(&WAV_DATA3 [pwm_state3 >> SHIFT_PWM])) >> pwm_shift)

(4)波形をTimer1に書く・・・6.9us
Timer1.setPwmDuty(VVVF_SOUND_PIN,a);

(5)波形の次のポイントへの移動・・・2.5us
pwm_state1 += pwm_add1;
if((pwm_state1 >= (WAV_LENGTH1 << SHIFT_PWM)))
pwm_state1 -= (WAV_LENGTH1 << SHIFT_PWM);
pwm_state3 += pwm_add3;
if((pwm_state3 >= (WAV_LENGTH3 << SHIFT_PWM)))
pwm_state3 -= (WAV_LENGTH3 << SHIFT_PWM);

仕事の改善提案ではありませんが・・・、明らかに(4)のDutyをTimer1に書くところが
やっていることに対して時間をとりすぎな感じがプンプンします。
ということで、Timer1ライブラリのsetPwmDutyを眺めてみました。
すると、
void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) {
unsigned long dutyCycle = pwmPeriod;
dutyCycle *= duty;
dutyCycle >>= 10;
if (pin == TIMER1_A_PIN) OCR1A = dutyCycle;
・・・
となっており、long型の掛け算をやっており、これは遅そうだと思いました。
 Timer1は16ビットのタイマーで、もともとTimer1の周波数を変更してVVVF音の音階を変えようと思っていたのですが、いろいろと試行錯誤をやっているうちに、パルス周期は一定で波形周期だけ変える形に変更したため、Timer1の高機能は現在いらなくなっています。32kHzだけ出ればいいんです。
 ということで、Timer1を取っ払ってしまうことにしました。
具体的には、
void VVVF_Setup()
{
//PWM出力ピン D9を出力にセット
pinMode(VVVF_SOUND_PIN, OUTPUT);
//Timer1使用
Timer1.initialize();
//初期設定
Timer1.pwm(VVVF_SOUND_PIN,0,0);
Timer1.attachInterrupt(vvvf_int1,current_period1);//interruptの設定
}
部分を、analogWriteでも使用している8ビットタイマーにしてしまっています。
以下になります。

void VVVF_Setup()
{
//PWM出力ピン D9を出力にセット
pinMode(VVVF_SOUND_PIN, OUTPUT);

//31KHzに設定(初期設定でPhaseCorrectPWMモードになっていると思う)
//D9,D10 キャリア周期:31kHz(分周無し)
TCCR1B &= B11111000;
TCCR1B |= B00000001;

//出力の設定
if(VVVF_SOUND_PIN == 9)
{
TCCR1A |= _BV(COM1A1);
}
else //10
{
TCCR1A |= _BV(COM1B1);
}

//interrupt設定
TIMSK1 = _BV(TOIE1);
}

で、割り込みはAVRでよく見る形に変えています。
関数名をポインタで指定するやり方がよくわからなくって・・・。
ISR(TIMER1_OVF_vect)
{
・・・
unsigned int a = ((pgm_read_byte_near(&WAV_DATA1 [pwm_state1 >> SHIFT_PWM]) * 3 + pgm_read_byte_near(&WAV_DATA3 [pwm_state3 >> SHIFT_PWM])));

if (VVVF_SOUND_PIN == 9)
{
OCR1A=a>>(pwm_shift + 2);
}
else//(VVVF_SOUND_PIN == 10)
{
OCR1B=a>>(pwm_shift + 2);
}
・・・
}

で、これでめでたく、割り込み時間が短くなりました。
EL102dスケッチ(こちら)になります。
走行時
EL102d走行時(Timer1やめ).png

停止時
EL102d停止時(Timer1やめ).png

走行時は周期が31usぐらいで、割り込み時間は10.4us、0.8usを繰り返します。
停止時は周期が31usぐらいで、割り込み時間は1,3us、0.8usを繰り返します。

 一応、世間的にいうと17.1us→10.4usなので「性能は1.5倍アップ!」です。
 でも、たぶん、割り込みのために、割り込み前、割り込み後に必要な退避とかがあるでしょうから、そんなにアップしてないんだろうなあとは思います。


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。