455kHz発振器
参考にした
部品
16MHz水晶発振子
ATTiny85
回路図
https://scrapbox.io/files/614c9ae1c02c320021bec596.png
可変抵抗R2で変調度を設定(0-100%)
可変抵抗R7で出力を設定(0-100%)
特徴
水晶を使ってるので正確
16MHz / 211 * 6 = 454.976kHz
1kHzのオーディオ信号で455kHz搬送波を振幅変調
TIMER1のPWMで一度に生成
コード
code:siggen-455.ino
//
// siggen 455kHz
// for ATtiny85@16MHz
//
void setup()
{
pinMode(1, OUTPUT); // OC1A
TCCR1 = _BV(PWM1A) | _BV(COM1A1) | _BV(COM1A0) | _BV(CS10);
TIMSK |= _BV(TOIE1);
PLLCSR &= ~_BV(PCKE);
GTCCR = 0;
OCR1A = 123;
OCR1C = 210; // 16MHz / 211 = 75.829kHz
}
unsigned int volume = 128;
void loop()
{
volume = analogRead(A1) >> 2;
}
unsigned int phase;
unsigned char phase_correction;
//print("{")
//for x in range(16):
//  print("  ", end="")
//  for y in range(16):
//    t = x * 16 + y
//    s = math.sin(2 * math.pi * t / 256)
//    print(f"{int((s + 1.0) * 128)}, ", end="")
//  print()
//print("};")
//
// replace 256 -> 255
const byte sin_table256 PROGMEM = {
128, 131, 134, 137, 140, 143, 146, 149, 152, 156, 159, 162, 165, 168, 171, 174, 
176, 179, 182, 185, 188, 191, 193, 196, 199, 201, 204, 206, 209, 211, 213, 216, 
218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 237, 239, 240, 242, 243, 245, 
246, 247, 248, 249, 250, 251, 252, 252, 253, 254, 254, 255, 255, 255, 255, 255, 
255, 255, 255, 255, 255, 255, 254, 254, 253, 252, 252, 251, 250, 249, 248, 247, 
246, 245, 243, 242, 240, 239, 237, 236, 234, 232, 230, 228, 226, 224, 222, 220, 
218, 216, 213, 211, 209, 206, 204, 201, 199, 196, 193, 191, 188, 185, 182, 179, 
176, 174, 171, 168, 165, 162, 159, 156, 152, 149, 146, 143, 140, 137, 134, 131, 
128, 124, 121, 118, 115, 112, 109, 106, 103, 99, 96, 93, 90, 87, 84, 81, 
79, 76, 73, 70, 67, 64, 62, 59, 56, 54, 51, 49, 46, 44, 42, 39, 
37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10, 
9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, 
9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, 
37, 39, 42, 44, 46, 49, 51, 54, 56, 59, 62, 64, 67, 70, 73, 76, 
79, 81, 84, 87, 90, 93, 96, 99, 103, 106, 109, 112, 115, 118, 121, 124, 
};
//dn = sorted(filter(lambda x: x2 >= 0, en), key=lambda x: -x1) //i = 0
//print("{")
//for x in range(16):
//  print("  ", end="")
//  for y in range(16):
//    t = 2 - (x * 16 + y) / 128
//    while i < 210 and dni1 > t: //      i += 1
//    print(f"{dni0}, ", end="") //  print()
//print("};")
const byte duty_table256 PROGMEM = {
123, 52, 122, 157, 192, 16, 51, 86, 121, 156, 156, 191, 15, 15, 50, 50, 
85, 120, 120, 155, 155, 190, 190, 190, 14, 14, 49, 49, 84, 84, 84, 119, 
119, 154, 154, 154, 189, 189, 13, 13, 13, 48, 48, 48, 83, 83, 83, 118, 
118, 118, 153, 153, 153, 188, 188, 188, 12, 12, 12, 47, 47, 47, 82, 82, 
82, 82, 117, 117, 117, 152, 152, 152, 187, 187, 187, 187, 11, 11, 11, 46, 
46, 46, 46, 81, 81, 81, 116, 116, 116, 116, 151, 151, 151, 151, 186, 186, 
186, 10, 10, 10, 10, 45, 45, 45, 45, 80, 80, 80, 80, 115, 115, 115, 
150, 150, 150, 150, 185, 185, 185, 185, 9, 9, 9, 9, 44, 44, 44, 44, 
79, 79, 79, 114, 114, 114, 114, 149, 149, 149, 149, 184, 184, 184, 184, 8, 
8, 8, 8, 43, 43, 43, 78, 78, 78, 78, 113, 113, 113, 113, 148, 148, 
148, 183, 183, 183, 183, 7, 7, 7, 7, 42, 42, 42, 77, 77, 77, 77, 
112, 112, 112, 147, 147, 147, 147, 182, 182, 182, 6, 6, 6, 6, 41, 41, 
41, 76, 76, 76, 111, 111, 111, 111, 146, 146, 146, 181, 181, 181, 5, 5, 
5, 40, 40, 40, 75, 75, 75, 110, 110, 145, 145, 145, 180, 180, 180, 4, 
4, 39, 39, 39, 74, 74, 109, 109, 144, 144, 179, 179, 179, 3, 3, 38, 
73, 73, 108, 108, 143, 143, 178, 2, 37, 37, 72, 107, 142, 177, 36, 71, 
};
ISR(TIMER1_OVF_vect)
{
phase += 864; // 1/75.829kHz : 1/1kHz = phase_inc : 2^16
phase_correction = !phase_correction;
byte wave = pgm_read_byte_near(&sin_tablephase >> 8); OCR1A = phase_correction ? pwm : 211 - pwm;
}
波形
変調度0%
https://scrapbox.io/files/614ca7053dba280023b364fc.png
https://scrapbox.io/files/614ca6f45929d80023bab57a.png
変調度50%
https://scrapbox.io/files/614ca74b0b7215001d1e008b.png
https://scrapbox.io/files/614ca7429abce800219c3f87.png
変調度100%
https://scrapbox.io/files/614ca780477874001e9caee6.png
https://scrapbox.io/files/614ca7888e7ca6001dd99bd2.png
仕組み
PWM搬送波周波数 $ f_{\rm pwm} = 16 \mathrm{\,[MHz]} / 211 = 75.829 \mathrm{\, [kHz] } 
RF周波数$ f_{\rm rf} = 6 f_{\rm pwm} = 454.976 \mathrm{\, [kHz]} 
PWM搬送波周波数の矩形波をデューティー比$ Dで生成する
フーリエ級数展開すると6倍波$ f_{\rm rf} の係数は
$ c_6 = \frac{1}{12\pi}\sin(12\pi D) - \frac{1}{12\pi}(1 - \cos(12\pi D))\sqrt{-1} 
振幅は
$ |c_6| = \frac{\sqrt{2}}{12\pi}|\sin(6\pi D)|
となる。時間ごとに$ Dを変化させることでAM変調が実現できる。
$ Dを動かしたときの振幅をプロットするとこうなる(以下定数倍は無視)。
https://scrapbox.io/files/6151cf1b4e99fa001dddd660.png
これを見ると、$ 0 \le D \le \frac{1}{12}の間だけ動かせばよくて、他の$ Dの値を使う意味はないように思える。
タイマーを使ったPWMでは$ Dの値は$ \frac{0}{211}, \frac{1}{211}, \dots, \frac{211}{211}しか取ることができないことを考えると、これは$ \frac{0}{211}, \frac{1}{211}, \dots, \frac{35}{211}の範囲を使うということになるので、36値の分解能しかないように見えるかもしれない。
これは正しくなくて、$ D=\frac{0}{211}, \frac{1}{211}, \dots, \frac{211}{211}に対して$ c_6を複素平面上にプロットすると
https://scrapbox.io/files/6151d16f6bb14a001d144b3d.png
このように綺麗に円周上に均一に並んでいることがわかる。211と6が互いに素なのでこうなる。拡大してみると、
https://scrapbox.io/files/6151d1cc3d7a1800457ce81a.png
こんな感じ。なので、$ D=\frac{0}{211}, \frac{1}{211}, \dots, \frac{211}{211}を余さず使うことができる。上の実装では実部が正の点と負の点を交互に出力することで、$ \arg{c_n} が90度を維持するようにしている(AVRのPhase Correct PWMと同じ)。