さて、Arduinoの動作周波数から、以前のやり方では、どうしても高い周波数のSin波を出力することが難しかった。(最高でも910Hz)このサイトの記事を見ると、16kHzまで出せるという。これはやりかたを勉強しなくてはならない。
お手本は 大輔 岡氏の
https://qiita.com/daisukeokaoss/items/787dab2371d29a82308f
元ネタが
http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-dds-sinewave-generator/
である。
いくつかのポイントがあり、
(1)Sin波形データを用意してそれを順次取り出して出力する考え方は同じ。
(2)Sin波形の周波数を極めて正確にすることが一つのポイント。(Direct Digital Synthesis (DDS) )
(3)そのために、内部のタイマーによる割り込みを使っている。
(4)出力はPWM(Pulese Width Modulation)で、基本的にLPF(Low Pass Filter)を介してSin波形を出力させる。
(5)最高16kHzを出力させるため、ライブラリーを使うことなく、直接にマイコンを制御している。コマンドも工夫されている。
それぞれ補足すると、
(1)前は普通に配列を用意してSinデータを保存していたが、この例では、少ない(UNOでは1kbyteしかない)SRAM領域を使わずに、プログラム領域にデータを保存している。
(スケッチ冒頭に#include "avr/pgmspace.h"とあるが、以下のArduino Referenceにある通り、IDEバージョンが1.0以下だと必要なのであって、それ以降であれば不要。
PROGMEM is part of the pgmspace.h library. It is included automatically in modern versions of the IDE, however if you are using an IDE version below 1.0 (2011), you’ll first need to include the library at the top your sketch, like this:)
(4)D/A変換は使用していない。
(5)Arduinoではなく、AVRマイコンのことを書いていたりするので、端子名称など、混同しないようにしないといけない。
大輔 岡氏のスケッチのうち、
dfreq=1.0; // initial output frequency = 1000.o Hz
の部分を
dfreq=1000.0; // initial output frequency = 1000.o Hz
として出力を見る。
LCフィルターがないので、図のようなCRフィルターで見ると、一応1kHzのようなSin波が出ているのが確認できた。(CRフィルターなので、出力レベルは低い)
![]() |
横軸は1目盛り100μs |
まずは少しずつプログラムを解析していくことにする。
正確なタイミングでSin波を出すための割り込みについて、内臓のタイマーカウンターも含め以下の「うしこ」さんのサイトが詳しい。
http://usicolog.nomaki.jp/engineering/avr/avrInterrupt.html
http://usicolog.nomaki.jp/engineering/avr/avrPWM.html
タイマーカウンターと割り込みの動作
タイマーカウンターは、とても複雑で(機能豊富で)なかなか理解ができない。
ともかく今回はTimer2をつかっているので、そこだけを注目して見ていく。
Timer2はTimer0と同じく8bitのカウンターである。これをクロックでカウントアップしていくので、0〜255までカウントする。Arduino UNOのクロックは16MHzだが、カウンターのクロックは16MHzではないようだ。
以下のスケッチで確認すると、
内容をざっと説明する。
setup()でタイマーカウンターの動作を設定する。10,11行にある、TCCR2A,TCCR2Bレジスタの各ビットが意味を持ち、(内容の詳細は前述の「うしこ」さんのサイト参照)タイマーカウンターのクロックは分周なしに、直接カウンターを駆動(スピードが最も早い)、カウンターの値がレジスタのOCR2Aと同じになったら、PD11の出力をLにするように設定した。この例では0〜0xFF(255)までカウントアップしていく途中の中間値をレジスタOCR2Aにセット、PD11をオシロスコープで見ると、HとLが半分ずつ(duty50%)になるはずである。
Timer2がオーバーフローしたら(すなわち0xFFになったら)割り込みが発生し、この関数(23行)を動かす。今回はPD7をON,OFFさせている。通常であれば
digitalWrite (7, HIGH ); |
digitalWrite (7, LOW ); |
PD7の出力をオシロスコープで見ることで、Timer2がどれくらいの周期で動いているのかがわかるはずである。
オシロスコープの波形は以下の通りである。
横軸は1メモリ10μSなので、Timer2が0〜0xFF(255)までカウントアップするのに、約32μSかかっていることが分かる。
1つカウントアップするのに、32/256(μS)かかっているので、カウンターのクロック周波数は
256/32(MHz)=8(MHz)となり、クリスタルの16MHzの半分であることが分かる。
どうやったらアナログ値を出力できるのか
PWMなので、Hの時間が長いと(H=5Vとして)平均値は5Vに近くなり、短いと0Vに近くなる。先のスケッチで言えば、OCR2Aに0xFF(255)をセットすれば5Vになり、0をセットすれば0Vになる。半分の0x7F(127)をセットすれば、2.5Vになる。
Sin波形データは0〜255の範囲にあるので、このデータを順にOCR2AにセットしていくことでSin波形が出力されることがわかる。
その時の周波数は
Sin波形のデータは全部で256個ある。これを32μSごとにOCR2Aにセットしていくと、1周期は32×256(μS)となり、その周波数は1/(32×256)(MHz)すなわち約122Hzにしかならない。どうやったら1kHzや16kHzが出るのか。
ここでサイト上にある、波形の写真をよく見てみよう。
⊿t=33.3μs f=33.3kHzと表示されているが、おそらく上のPWM波形の周期を測っているのではないか。(2つの小さなクロス)先のテストでもPWMの周期が32μsなので、たぶん間違いない。
では、下のSin波の周期はというと、先の33.3μsからすると1目盛り100μsのようなので、半サイクルで約5目盛り。(つまり500μs)1サイクルで1msすなわち1kHzとなる。
スケッチのsetup()の部分に
dfreq=1000.0; // initial output frequency = 1000.o Hz
とあるので、1kHzは出るのだろう。
(ただ、スケッチを追っていくと、出力周波数は
dfreq=analogRead(0); // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hzというloop()にある一文によって自由に(0〜1,023Hz)変えることができるようである。
大輔 岡氏はこの部分を使わず、setup()で
dfreq=1.0; // initial output frequency = 1000.o Hz
と変更して、1HzのSin波を出力させている。そのことはコメントで記載している。)
DDSの動作を含め、冒頭のスケッチの解析については改めて。