arduinoにはPWM波が作れるライブラリがありますが、周期は変更できないようなので、AVR内蔵のタイマを直接制御して38KHzの矩形波を作ってみました。38KHzというのは赤外線リモコンなどで使うキャリア波です。
PWMでも矩形波は作られますが、今回は16ビットのTIMER1をCTCモードで使ってみました。デューティは50%です。
●CTC
簡単に説明しておくと、CTCというのは、システムクロックなどのクロックをカウンタ/タイマでカウントして、設定した数値(コンペア値)になると、カウンタを自動的にリセットして、また、0からカウントを繰り返すいうよなインターバル動作をするものです。
また、コンペア値になった時に割込みを発生させたり、規定の出力ポートの状態を反転させることができます。この出力ポートの反転は、CTCの周期で発生します。これが矩形波となります。
●Arduinoのタイマのアサイン
直接レジスタを操作するため、PWMライブラリで特定のポートが使えなくなります。どのPWMがどのポートを使っているかをArduinoのピン配置とAVRのピン配列から調べてみました。
タイマ | AVRポート | Arduinoポート |
TIMER0(8bit) | PD5/OC0B | D5 |
TIMER0(8bit) | PD6/OC0A | D6 |
TIMER1(16bit) | PB1/OC1A | D9 |
TIMER1(16bit) | PB2/OC1B | D10 |
TIMER2(8bit) | PB3/OC2A | D11 |
TIMER2(8bit) | PD3/OC2B | D3 |
●今回TIMER2を使用
今回は周波数が高いのと、正確にしたかったために、16bitのTIMER1を使うことにします。OC0Aの方を使うため、D9のPWM機能は使えません。D10はおそらく使えると思いますが、プリスケーラをいじっているので、周期が変わると思います。
CTCはプログラムで割込みを周期的に発生するインターバルタイマとして使用しますが、あるレジスタを適当な値に設定すると、その状態をディジタル出力ポートに反映させることができます。
●周期
周期は、タイマのクロックソースの周波数と、カウント最大値(TOP値)により決まります。TOP値はコンペアレジスタOCR1Aに設定しますが、規定の周波数を得るには半分の値にする必用があります(一周期で2回反転させるため)。また、カウントの関係で、実際の計算値より-1したものを設定します。システムクロックが16MHzのときの各値を示します。
プリスケーラ値 | 1クロックの周期 | OCR1A設定値 | 生成周波数 |
1 | 0.0625us | 207 | 38KHz |
64 | 4us | 24 | 5KHz |
逆算すると、
F = 1 / (0.0625us * 208 * 2) = 38.46kHz
F = 1 / (4us * 25 * 2) = 5KHz
となります。
●サンプルスケッチ
詳細は省略します。レジスタの設定値などはソースをみてください。このプログラムでは38KHzと5KHzを作っています。切り替えるには66行目の関数の引数を書き換えて再コンパイルします。’0′が5KHz、’1′が38KHzです。
矩形波が出力されるポートはD9です。このポートはあらかじめ出力に設定しておく必用があります。
// // キャリア波発生 TIMER1 16bit CTCモード // // 14/04/30 // // OC1A(PB1/D9) 38KHz/5KHz 実測により動作確認済 // void initCTC1(int carry) { pinMode(9, OUTPUT); // これ必要 if(carry) { // ************ 38KHz *************** // 16MHz * 1/1 = 16MHz // T = 1 / 16MHz = 0.0625us // 逆算 F = 1 / (0.0625 * 208 * 2) = 38.46kHz // OC1A(PB1/D9) toggle TCCR1A &= ~(1<<COM1A1); // 0 TCCR1A |= (1<<COM1A0); // 1 // WGM13-10 = 0100 CTCモード TCCR1B &= ~(1<<WGM13); // 0 TCCR1B |= (1<<WGM12); // 1 TCCR1A &= ~(1<<WGM11); // 0 TCCR1A &= ~(1<<WGM10); // 0 // ClockSource CS12-CS10 = 001 16MHz / 1 T= 0.0625us TCCR1B &= ~(1<<CS12); // 0 TCCR1B &= ~(1<<CS11); // 0 TCCR1B |= (1<<CS10); // 1 OCR1A = 207; // コンペア値 } else { // ************ 5KHz *************** // 16MHz * 1/64 = 0.25MHz T = 4us // 250KHz / 5KHz = 50 // コンペア値 = 50 / 2 = 25 // 逆算 F = 1 / (4 * 25 * 2) = 5KHz // OC1A(PB1/D9) toggle TCCR1A &= ~(1<<COM1A1); // 0 TCCR1A |= (1<<COM1A0); // 1 // WGM13-10 = 0100 CTCモード TCCR1B &= ~(1<<WGM13); // 0 TCCR1B |= (1<<WGM12); // 1 TCCR1A &= ~(1<<WGM11); // 0 TCCR1A &= ~(1<<WGM10); // 0 // ClockSource CS12-CS10 = 011 16MHz / 64 T= 4us TCCR1B &= ~(1<<CS12); // 0 TCCR1B |= (1<<CS11); // 1 TCCR1B |= (1<<CS10); // 1 OCR1A = 24; // コンペア値 } } void setup(void) { // analogWrite(9, 100); initCTC1(0); } void loop(void) { }
●どうやって使うか
赤外線で通信したいとき、私はよく、UART(USART)を利用します。変調するのにゲート回路が必要になりますが、HC00とか、トランジスタ1個でつくられます。
ANDゲートの片側にUARTのTX出力、もう一方にキャリア波38KHzを加えて、通常のシリアル通信を行えば、調歩同期の波形で変調された波形が得られます。これで赤外線LEDを発光させます。
受ける側は、赤外線受光モジュールをUARTのRXへつなぐだけです。
赤外線リモコンなどでは、キャリア波の発生と調歩同期やPWMなどの波形生成をソフトだけで作ることが多いと思いますが、このハードウェア変調方式だとプログラムの処理がものすごく簡単になります。
赤外線リモコンのように送信することが第一の目的で、送信処理に専念できる場合はソフトでやる方がいいと思いますが、通信以外にいろいろ平行して処理させたい場合、こういった送受信処理にあまり手間をかけられないので(パルス幅を確保するためのディレイとか、受信待ちとかが問題になる)、ハードウェア変調は有効です。