Arduinoで和音を出力する方法 その2

#前回のあらすじ
<font color="#ff8000">**Tone()**</font>関数をつくった。

https://qiita.com/electronic1155work/items/8afb499cd8c5cbf5e6ee

#さて
<img width="800" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/b97de16c-4ce5-7b49-138b-92407c8027e0.png">

これは前回の、スケッチの<font color="#ff8000">D13</font>の出力です。<font color="red">440Hz</font>が出力されていることが確認できます。

ところで、割り込みの周期はいくつでしょうか

<img width="800" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/f5d1b802-c9f2-c947-d633-ac934fbfc52c.png">

割り込みが発生すると、ピンの状態が変化するので
この赤丸で囲った部分で割り込みが起きています。

つまり、割り込みの周期は2倍の<font color="red">880Hz</font>ということになります。

よって、割り込み周期の式は

```math
\begin{align}
f_{interrupt}[Hz]&=\frac{f_{clk_{I/O}}}
{Count\times (OCR2A+1)} \\
\end{align}

```

となります。

#今回のスケッチ

今回使用するスケッチを示します

```C:multi_output

#define output_pin_A 11
#define output_pin_B 12
#define output_pin_C 13

#define DIV_1 1 //分周無し
#define DIV_8 2 //8分周
#define DIV_32 3 //32分周
#define DIV_64 4 //64分周
#define DIV_128 5 //128分周
#define DIV_256 6 //256分周
#define DIV_1024 7 //1024分周
#define WGM21 0b10 //タイマ2 動作モード設定
#define TOIE2 0b10 //割り込み許可

volatile int operator_A = 0;
volatile int operator_B = 0;

volatile int operator_A_add = 720;
volatile int operator_B_add = 360;

void setup() {
  //ピンの設定
  pinMode(output_pin_A, OUTPUT);
  pinMode(output_pin_B, OUTPUT);
  pinMode(output_pin_C, OUTPUT);

  //タイマの設定
  TCCR2A = WGM21;//CTCモード　比較一致OCR2A
  TCCR2B = DIV_8;
  OCR2A = 99;//20kHz
  TIMSK2 = TOIE2;

  sei();//割り込み許可
}

void loop() {
}

ISR(TIMER2_COMPA_vect) {
  //割り込み時に実行される関数
  digitalWrite(output_pin_C, !digitalRead(output_pin_C));
  
  operator_A += operator_A_add;
  operator_A &= 0x7FFF;
  operator_B += operator_B_add;
  operator_B &= 0x7FFF;

  if (operator_A < 0x3FFF) {
    digitalWrite(output_pin_A, LOW);
  } else {
    digitalWrite(output_pin_A, HIGH);
  }

  if (operator_B < 0x3FFF) {
    digitalWrite(output_pin_B, LOW);
  } else {
    digitalWrite(output_pin_B, HIGH);
  }
  
  digitalWrite(output_pin_C, !digitalRead(output_pin_C));
}
```
このスケッチを書き込むと次のような出力が得られます

<img width="800" alt="スケッチ出力結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/1d0d76cc-eb5b-1998-4706-c7513373babd.png">

###volatile とは

```C
volatile int operator_A = 0;
volatile int operator_B = 0;

volatile int operator_A_add = 720;
volatile int operator_B_add = 360;
```

この、<font color="red">volatile</font>は割り込み処理内で使用する、グローバル変数の型の前に必ずつけます。
これがないと、変数の中身が毎回初期値に戻ってしまいます。

コンパイラの最適化が原因なのでvolatileを付けてコンパイラに最適化させないよう指示をしています。


###割り込み周波数
まず、Timer2の割り込みを<font color="DeepPink">20kHz</font>に設定しています。

```math
\begin{align}
f_{interrupt}[Hz]&=\frac{f_{clk_{I/O}}}
{Count\times (OCR2A+1)} \\

OCR2A&=\frac{f_{clk_{I/O}}}
{Count\times f_{interrupt}[Hz]} -1\\

OCR2A&=\frac{16 \times 10^6}
{8\times 20 \times 10^3 } -1\\

&=99\\

\end{align}

```

とうことなので、<font color="red">99</font>になります。

オシロの画像からも、<font color="#ff8000">D13</font>から<font color="DeepPink">20kHz</font>が出力されていることがわかります。

###ISR内
今回も前回と同様に<font color="darkgreen">TIMER2_COMPA_vect</font>を使用します。

```C:D13
ISR(XXX){
  digitalWrite(output_pin_C, !digitalRead(output_pin_C));
  //処理
  digitalWrite(output_pin_C, !digitalRead(output_pin_C));
}
```
<font color="#ff8000">D13</font>のピン操作を、割り込み処理内の初めと終わりに入れているのは
処理時間をオシロスコープ等で計測するためです。

割り込み処理に時間がかかると、次の割り込みの時間が来てしまい
loopに戻ることができなくなってしまいます。

また、割り込み処理に時間を取られるとloop内の処理やその他の割り込みに割く時間が無くなってしまいます。

:::note info
余談
<font color="#ff8000">D13</font>にLEDを付けると、負荷率を可視化できます。
アナログメータを付けるのもおすすめです。
:::

####DDS　ダイレクト・デジタル・シンセサイザー
スケッチ内の

```C:DDS
ISR(XXX){
  operator_A += operator_A_add;
  operator_A &= 0x7FFF;
  operator_B += operator_B_add;
  operator_B &= 0x7FFF;
}
```
では、ダイレクト・デジタル・シンセサイザーを実装しています。

https://ja.wikipedia.org/wiki/%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%83%BB%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E3%83%BB%E3%82%B7%E3%83%B3%E3%82%BB%E3%82%B5%E3%82%A4%E3%82%B6

名前こそ難しいですが、やっていることは単純で

<font color="red">内部のデジタル値を一定周期で増加させているだけです。</font>

タイマ割り込みでは、分周器と上限値を変えて周波数を変更しました。
DDSでは、上限を決め加算する数を変えて周波数を決定します。

どのようにして、出力周波数が決定されるか追っていきましょう

```C:DDS_1
ISR(XXX){
  operator_A += operator_A_add;
  operator_A &= 0x7FFF;
}
```
operator_A がAというオペレータ、位相に当たります。
operator_A_addが加算量、割り込み一回ごとに加算されていきます。

```C
  operator_A &= 0x7FFF;
```

0x7FFFは、16進数表記です。10進数に直すと、

<img width="300" alt="スケッチ出力結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/58f6d997-6dc9-8f3a-9489-9166c85d2120.png">

```math 
7FFF_{(16)}=32767=2^{15}-1=111 1111 1111 1111_{(2)}
```
となります。

今回、オペレータには<font color="blue">符号ありint型</font>を使用したのでその最大数ですね。

```C
  operator_A &= 32767;
```

であることがわかりましたが、これはいったい何かというと

```C
  operator_A = operator_A & 0b111111111111111;
```

という計算になります。もっとかみ砕くと

```C
  operator_A = operator_A % 32767;
```

と、剰余算になります。

オペレータを加算していくと、いつか桁あふれを起こし<font color="blue">マイナス</font>の値になってしまいます。
それを防ぐために、リミット処理を施しています。

剰余算をビット計算に置き換える手法はwikiにも記載があります。

https://ja.wikipedia.org/wiki/%E5%89%B0%E4%BD%99%E6%BC%94%E7%AE%97

:::note info
余談
%を使った処理は、遅いです。試しに、&=を%=にしてみてください。
処理が間に合わなくなり、出力周波数が落ちるはずです。
:::

###出力周波数を得るための加算量の計算方法
ここから、厄介で自分もよく計算方法を忘れてしまいます。

イメージは下の図のようになります。
<img width="800" alt="スケッチ出力結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1836386/e00a63d5-3b7b-8eec-2f68-ffbeeb9a2bd5.png">

のこぎり波を作り、一定の大きさを超えたらHIGHに、下回ったらLOWにしています。

まず、オペレータが0から加算していき何回目でオーバーフローするか計算します

```math
\begin{align}
OVF_{count}&=\frac{32767}
{Add} \\
\end{align}
```

つまり、上限数を加算量で割ればいいことになります。

今回の場合

```math
\begin{align}
OVF_{count}&=\frac{32767}
{Add} \\
OVF_{count}&=\frac{32767}
{720} \\
&=45.5\\
\end{align}
```

と、おおよそ46回でオーバーすることがわかりました。

オーバーフローする回数がわかったということは、オーバーフローするまでの時間もわかります。

```math
\begin{align}
OVF_{time}&=t_{sample}\times OVF_{count}\\

OVF_{time}&=\frac{1}
{f_{sample}} \times OVF_{count}\\

\end{align}
```

加算する時間は、割り込み周期なのでサンプリング時間をかけてあげればいいだけです

```math
\begin{align}
OVF_{time}&=t_{sample}\times OVF_{count}\\

OVF_{time}&=\frac{1}
{10^{10^3}} \times 45.5\\
&=0.002275\\

\end{align}
```

<font color="OrangeRed">0.002275秒</font>でオーバーフローすることがわかりました。
さて、肝心の周波数は

```math
f[Hz]={OVF_{time}}^{-1}=1 \div OVF_{time} = 1\div 0.002275=439.56Hz
```

ようやく、<font color="Magenta">440Hz</font>が出ることがわかりました。

ここまでの式を整理しましょう。

```math
\begin{align}
f_{output}&=\frac{1}{OVF_{time}}\\
&=\frac{1}{\frac{1}
{f_{sample}} \times OVF_{count}}\\

&=\frac{f_{sample}}{OVF_{count}}\\

&=\frac{f_{sample}}{\frac{32767}
{Add}}\\

&=\frac{f_{sample} \times Add}{32767}\\

\end{align}
```

となります。検算をしてみましょう。

```math
\begin{align}
f_{output}&=\frac{f_{sample} \times Add}{32767}\\

&=\frac{20 \times 10^3 \times 720}{32767}\\

&=439.4Hz
\end{align}
```

無事合っていましたね。

さてさて、Addが決まっていることは通常あり得ませんね。
任意の出力周波数の時の式に変形しましょう。

```math
\begin{align}
f_{output}&=\frac{f_{sample} \times Add}{32767}\\

f_{output}\times \frac{1}{Add}&=\frac{f_{sample} \times Add}{32767}\times \frac{1}{Add}\\

f_{output}\times \frac{1}{Add}&=\frac{f_{sample}}{32767}\\

f_{output}\times \frac{1}{Add} \times \frac{1}{f_{output}}&=\frac{f_{sample}}{32767} \times \frac{1}{f_{output}}\\

\frac{1}{Add} &=\frac{f_{sample}}{32767} \times \frac{1}{f_{output}}\\

\frac{1}{Add} &=\frac{f_{sample}}{32767 \times f_{output}} \\

{Add} &=\frac{32767 \times f_{output}}{f_{sample}} \\

\end{align}
```

となります。

検算しましょ～

```math
\begin{align}
{Add} &=\frac{32767 \times f_{output}}{f_{sample}} \\

&=\frac{32767 \times 440}{20 \times 10^3} \\

&= 720.874

\end{align}
```
合ってますね。

#まとめ
これでようやく、DDSの説明が終わりました。

出力される周波数を、変えたいときは割り込み周期を変えるか、加算値を変えればいいことがわかっていただけたでしょうか。
これさえ、理解できればマイコンだろうがFPGAだろうがなにが来てもすぐにシンセサイザーにすることができます。(たぶん)

>この式大事
>
```math
\begin{align}
{Add} &=\frac{32767 \times f_{output}}{f_{sample}} \\
\end{align}
```
>

```C:DDS
ISR(XXX){
  operator_A += operator_A_add;
  operator_A &= 0x7FFF;
  operator_B += operator_B_add;
  operator_B &= 0x7FFF;
}
```

このオペレータの数を増やせばそれだけ多くの周波数を出力することができます。
今回は、D12,D11の2つでしたが<font color="#00979c">**Arduino**</font>にはまだまだピンがあります。
処理時間の関係で無限には難しいですが。10音くらいは出せると思います。

試してみてください。

#次回
今回は、確かに和音ではありますが
その音の数だけスピーカーないしは、ミキサーが必要ですね。

次回はそこを何とかしようと思います。

https://qiita.com/electronic1155work/items/02a1353d78211792d367

#おわりに
長文をよんでくださりありがとうございました。
6000文字も書いてしまったみたいです。気を付けます。