1kHzの正弦波をDDSで発生
ちょいと仕事関係の「治具」製作の予備実験。
こんなのの出力波形を模擬します。
風速、毎秒98メートル(むちゃ暴風だ)で1kHzの信号が出てくることになっています。
扇風機で風を送っても、そんなには回りませんので、これのつなぎ先の実験には模擬的な信号が必要です。
低周波オシレータをつないで、周波数から風速を逆算すれば良いんですが、治具となるとお手軽な操作が必須です。
そして必要な信号は「正弦波」。
1Hz~1kHzが出て欲しい。
まぁ、まっとうには「DDS」 用のICを使っての制御。
パルスをスイッチ・キャパシタ・フィルタを通して正弦波を得るなんて方法も考えられます。
アナデバのAD9833 を発注したんですが、手元にある部品だけでさっと作られないかと、試してみました。
最初はお手軽にArduino。
このページが参考になるかしら。
結局、Arduinoではなくこんな具合にATmega328Pで作ってみました。
・クロックは19.6608MHzに。
(Arduino UNOは16MHz)
・R-2R抵抗ラダーで8bit D/Aを構成。
2.5Vを中心に0~5Vを発生。
・周波数(風速)はデジタルスイッチで設定。
・最終出力は0Vを中心に、±5Vで出力。
・風速により出力レベルが変わるので、D/A値にVolume定数をかけて出力。
ざっとこんな回路に落ち着きました。
※これからケース入れ。
電池で運用しますんで、5Vへの昇圧回路や-5V発生回路、
Low BAT警報回路なんかも仕込みます。
ここで、DDSでの周波数関係を調べていると、「19.6608MHz」の水晶(シリアル通信ボーレート発生用として一般的)を使うと、「1Hz」単位の制御が割り切れるぞということが判明。
ここで、DDSでの周波数関係を調べていると、「19.6608MHz」の水晶(シリアル通信ボーレート発生用として一般的)を使うと、「1Hz」単位の制御が割り切れるぞということが判明。
DDSのカウンタ加算値の計算、
(「65536」 × 「sinテーブルのデータ数」 ÷ クロック周波数) × 発生周波数
となります。
となります。
65536は32ビットの下位16ビットを小数部として使うための乗数です。
sinデータテーブルは2のn乗値で今回は1024。
クロック周波数はがマイコンが処理するタイマー割り込みの周波数。
このクロック周波数が「19.6608MHz」の水晶だと1/300すれば「65536Hz」となり、16ビットの小数部の値と割り切れて1になり、sinテーブルをアクセスするための値が、1Hz=1024となって1Hz単位での計算だと割り切れるので周波数発生での誤差が出ません。
こりゃ知らなんだ。
問題は65536HzでCで書いた割り込み処理プログラムが動くかどうか・・・
まず、準備として正弦波データの作成。
JGAWK でさくっと作ります。 コマンドプロンプトで起動(笑)
# SINデータ作成
# ±120で作成 1024データ
BEGIN{
pai = atan2(0, -1) # π = 3.14の値
for(i = 0; i < 1024; i++){ # 1024データ
if((i % 10) == 0){ # 1行に10データ
n = i
c = 0
printf("\t")
}
printf("%4d,", 120*sin(((i / 1024) * 360) * pai / 180))
c++
if((i % 10) == 9){ # 10個目出力完了
printf("\t// %d\n", n)
}
}
while(c < 10){ # 最後の行
printf(" ")
c++
}
printf("\t// %d\n", n)
}
# ±120で作成 1024データ
BEGIN{
pai = atan2(0, -1) # π = 3.14の値
for(i = 0; i < 1024; i++){ # 1024データ
if((i % 10) == 0){ # 1行に10データ
n = i
c = 0
printf("\t")
}
printf("%4d,", 120*sin(((i / 1024) * 360) * pai / 180))
c++
if((i % 10) == 9){ # 10個目出力完了
printf("\t// %d\n", n)
}
}
while(c < 10){ # 最後の行
printf(" ")
c++
}
printf("\t// %d\n", n)
}
それをこんな具合にプログラム内に取り込みます。
/***** SIN 0~1023 *****/
// 中央値:128 振幅±120
const PROGMEM int8_t sin_tbl[]={
0, 0, 1, 2, 2, 3, 4, 5, 5, 6, // 0
7, 8, 8, 9, 10, 11, 11, 12, 13, 13, // 10
14, 15, 16, 16, 17, 18, 19, 19, 20, 21, // 20
21, 22, 23, 24, 24, 25, 26, 27, 27, 28, // 30
29, 29, 30, 31, 32, 32, 33, 34, 34, 35, // 40
// 中央値:128 振幅±120
const PROGMEM int8_t sin_tbl[]={
0, 0, 1, 2, 2, 3, 4, 5, 5, 6, // 0
7, 8, 8, 9, 10, 11, 11, 12, 13, 13, // 10
14, 15, 16, 16, 17, 18, 19, 19, 20, 21, // 20
21, 22, 23, 24, 24, 25, 26, 27, 27, 28, // 30
29, 29, 30, 31, 32, 32, 33, 34, 34, 35, // 40
: :
-17, -16, -16, -15, -14, -13, -13, -12, -11, -11, // 1000
-10, -9, -8, -8, -7, -6, -5, -5, -4, -3, // 1010
-2, -2, -1, -0, // 1020
};
-10, -9, -8, -8, -7, -6, -5, -5, -4, -3, // 1010
-2, -2, -1, -0, // 1020
};
DDS処理はタイマー1のコンペアマッチ割り込みで。
/***** タイマー1割り込み *****/
// DDD処理を実行
// dds_cntの上位16bit中10bitでサインテーブルを読み出す
SIGNAL(TIMER1_COMPA_vect)
{
int16_t d; // ±の値で
PB0_H; // (!!!) 14pin
dds_cnt += dds_step; // DDSカウンタをカウントアップ
d = (int16_t)((int8_t)pgm_read_byte(&sin_tbl[(dds_cnt >> 16) & 0x03FF]));
// 0~1023でsin波形テーブルをアドレス
d = d * dds_vol; // 出力ゲイン *0~*256
d = d / 256; // MSBを使う
PORTD = d + 128; // 128がD/A出力の中央値
PB0_L; // (!!!) 14pin
}
// DDD処理を実行
// dds_cntの上位16bit中10bitでサインテーブルを読み出す
SIGNAL(TIMER1_COMPA_vect)
{
int16_t d; // ±の値で
PB0_H; // (!!!) 14pin
dds_cnt += dds_step; // DDSカウンタをカウントアップ
d = (int16_t)((int8_t)pgm_read_byte(&sin_tbl[(dds_cnt >> 16) & 0x03FF]));
// 0~1023でsin波形テーブルをアドレス
d = d * dds_vol; // 出力ゲイン *0~*256
d = d / 256; // MSBを使う
PORTD = d + 128; // 128がD/A出力の中央値
PB0_L; // (!!!) 14pin
}
PB0に処理確認用パルスを出して、実行の様子をオシロで見られるようにしておきます。
こんな実行速度でした。
ATmega328P、頑張ってます。
HになっているのがDDS処理の実行部分。
HになっているのがDDS処理の実行部分。
前後にレジスタのpush、pop処理が入ります。
この割り込み処理、どんなふうにコード展開されているか見てみると・・・
この割り込み処理、どんなふうにコード展開されているか見てみると・・・
ちょいと長いですが・・・
SIGNAL(TIMER1_COMPA_vect)
668: 1f 92 push r1 PUSH,POPは2クロックで実行
66a: 0f 92 push r0
66c: 0f b6 in r0, 0x3f
66e: 0f 92 push r0
670: 11 24 eor r1, r1
672: 2f 93 push r18
674: 3f 93 push r19
676: 4f 93 push r20
678: 5f 93 push r21
67a: 6f 93 push r22
67c: 7f 93 push r23
67e: 8f 93 push r24
680: 9f 93 push r25
682: af 93 push r26
684: bf 93 push r27
686: ef 93 push r30
688: ff 93 push r31
------------------------------------------
68a: 28 9a sbi 0x05, 0 PB0テストパルス
68c: 80 91 11 01 lds r24, 0x0111 DDSカウント値
690: 90 91 12 01 lds r25, 0x0112
694: a0 91 13 01 lds r26, 0x0113
698: b0 91 14 01 lds r27, 0x0114
69c: 40 91 17 01 lds r20, 0x0117 DDSステップ値
6a0: 50 91 18 01 lds r21, 0x0118
6a4: 60 91 19 01 lds r22, 0x0119
6a8: 70 91 1a 01 lds r23, 0x011A
6ac: 84 0f add r24, r20 カウント値に
6ae: 95 1f adc r25, r21 ステップ値を加算
6b0: a6 1f adc r26, r22
6b2: b7 1f adc r27, r23
6b4: 80 93 17 01 sts 0x0117, r24
6b8: 90 93 18 01 sts 0x0118, r25
6bc: a0 93 19 01 sts 0x0119, r26
6c0: b0 93 1a 01 sts 0x011A, r27
6c4: cd 01 movw r24, r26 SIN波テーブルアドレス計算
6c6: aa 27 eor r26, r26
6c8: bb 27 eor r27, r27
6ca: 93 70 andi r25, 0x03
6cc: aa 27 eor r26, r26
6ce: bb 27 eor r27, r27
6d0: fc 01 movw r30, r24
6d2: e4 58 subi r30, 0x84
6d4: ff 4f sbci r31, 0xFF
6d6: 64 91 lpm r22, Z 8bit SIN波テーブル読み出し
6d8: 40 91 1f 01 lds r20, 0x011F
6dc: 50 91 20 01 lds r21, 0x0120
6e0: 64 03 mulsu r22, r20 VOLUME値を乗ずる
6e2: 90 01 movw r18, r0
6e4: 65 9f mul r22, r21
6e6: 30 0d add r19, r0
6e8: 11 24 eor r1, r1
6ea: 37 ff sbrs r19, 7
6ec: 02 c0 rjmp .+4 ;0x6f2
6ee: 21 50 subi r18, 0x01
6f0: 3f 4f sbci r19, 0xFF
6f2: 83 2f mov r24, r19
6f4: 80 58 subi r24, 0x80 +128して2.5V中心に出力
6f6: 8b b9 out 0x0b, r24 D/A出力
6f8: 28 98 cbi 0x05, 0 PB0テストパルス
------------------------------------------
6fa: ff 91 pop r31
6fc: ef 91 pop r30
6fe: bf 91 pop r27
700: af 91 pop r26
702: 9f 91 pop r25
704: 8f 91 pop r24
706: 7f 91 pop r23
708: 6f 91 pop r22
70a: 5f 91 pop r21
70c: 4f 91 pop r20
70e: 3f 91 pop r19
710: 2f 91 pop r18
712: 0f 90 pop r0
714: 0f be out 0x3f, r0
716: 0f 90 pop r0
718: 1f 90 pop r1
71a: 18 95 reti
668: 1f 92 push r1 PUSH,POPは2クロックで実行
66a: 0f 92 push r0
66c: 0f b6 in r0, 0x3f
66e: 0f 92 push r0
670: 11 24 eor r1, r1
672: 2f 93 push r18
674: 3f 93 push r19
676: 4f 93 push r20
678: 5f 93 push r21
67a: 6f 93 push r22
67c: 7f 93 push r23
67e: 8f 93 push r24
680: 9f 93 push r25
682: af 93 push r26
684: bf 93 push r27
686: ef 93 push r30
688: ff 93 push r31
------------------------------------------
68a: 28 9a sbi 0x05, 0 PB0テストパルス
68c: 80 91 11 01 lds r24, 0x0111 DDSカウント値
690: 90 91 12 01 lds r25, 0x0112
694: a0 91 13 01 lds r26, 0x0113
698: b0 91 14 01 lds r27, 0x0114
69c: 40 91 17 01 lds r20, 0x0117 DDSステップ値
6a0: 50 91 18 01 lds r21, 0x0118
6a4: 60 91 19 01 lds r22, 0x0119
6a8: 70 91 1a 01 lds r23, 0x011A
6ac: 84 0f add r24, r20 カウント値に
6ae: 95 1f adc r25, r21 ステップ値を加算
6b0: a6 1f adc r26, r22
6b2: b7 1f adc r27, r23
6b4: 80 93 17 01 sts 0x0117, r24
6b8: 90 93 18 01 sts 0x0118, r25
6bc: a0 93 19 01 sts 0x0119, r26
6c0: b0 93 1a 01 sts 0x011A, r27
6c4: cd 01 movw r24, r26 SIN波テーブルアドレス計算
6c6: aa 27 eor r26, r26
6c8: bb 27 eor r27, r27
6ca: 93 70 andi r25, 0x03
6cc: aa 27 eor r26, r26
6ce: bb 27 eor r27, r27
6d0: fc 01 movw r30, r24
6d2: e4 58 subi r30, 0x84
6d4: ff 4f sbci r31, 0xFF
6d6: 64 91 lpm r22, Z 8bit SIN波テーブル読み出し
6d8: 40 91 1f 01 lds r20, 0x011F
6dc: 50 91 20 01 lds r21, 0x0120
6e0: 64 03 mulsu r22, r20 VOLUME値を乗ずる
6e2: 90 01 movw r18, r0
6e4: 65 9f mul r22, r21
6e6: 30 0d add r19, r0
6e8: 11 24 eor r1, r1
6ea: 37 ff sbrs r19, 7
6ec: 02 c0 rjmp .+4 ;0x6f2
6ee: 21 50 subi r18, 0x01
6f0: 3f 4f sbci r19, 0xFF
6f2: 83 2f mov r24, r19
6f4: 80 58 subi r24, 0x80 +128して2.5V中心に出力
6f6: 8b b9 out 0x0b, r24 D/A出力
6f8: 28 98 cbi 0x05, 0 PB0テストパルス
------------------------------------------
6fa: ff 91 pop r31
6fc: ef 91 pop r30
6fe: bf 91 pop r27
700: af 91 pop r26
702: 9f 91 pop r25
704: 8f 91 pop r24
706: 7f 91 pop r23
708: 6f 91 pop r22
70a: 5f 91 pop r21
70c: 4f 91 pop r20
70e: 3f 91 pop r19
710: 2f 91 pop r18
712: 0f 90 pop r0
714: 0f be out 0x3f, r0
716: 0f 90 pop r0
718: 1f 90 pop r1
71a: 18 95 reti
push、popの時間合わせて3.5u秒くらい。
メインの処理が3u秒くらいなんで合わせて7u秒ほど。
割り込み周期15u秒の1/2ほどを使って、うまく実行できてます。
ケース入れが完了したら、あれこれまとめて報告しますね。
| 固定リンク
「電子工作」カテゴリの記事
- 電池ホルダーから電源供給するためのアダプタ(ダミー電池)#2(2023.03.24)
- 電池ホルダーから電源供給するためのアダプタ(ダミー電池)(2023.03.23)
- 単電源で反転アンプ マイナスの入力電圧は増幅できます(2022.11.28)
- 予告:「マイコン型導通チェッカー」「電池電圧チェッカー」値上げします(2022.11.16)
- ダイソー ミニケース 5個組の加工(2022.10.23)
コメント