アナログ出力の解説

# はじめに
ここでは、Arduino UNOで **<font color=purple>アナログ出力</font>** の実験をします。

目次へ戻るには [ここ](https://qiita.com/hsgucci/items/085680123f758c8c3eb4#%E5%AE%9F%E9%A8%93%E6%89%8B%E9%A0%86) をクリック

※本ページは実験のテキストです。

# アナログ出力

前回は、[アナログ入力の解説](https://qiita.com/hsgucci/items/6dc63b79f528d50870ac)をしました。

動作確認をする際、可変抵抗のツマミを回すとLEDの明るさが変化していたことに気付きましたか？
もう一度動作させて確認しましょう。
![aios_vr_led.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/6da7d055-5656-ff18-3c40-b3123e0356f9.png)

アナログ値が1023の時にはLEDは最も明るく、0の時には消灯します。
LEDに出力している電圧が変化しているので、明るさが変化しているのです。

今回は、この機能について解説します。

実際の作業としては

•【ソフトウェアの作業】プログラム解説

だけになります。

# スケッチの解説
AnalogInOutSerialのプログラムを再掲しておきます。

```cpp:AnalogInOutSerial.ino
/*
  アナログ入力, アナログ出力, シリアル出力

  アナログ入力ピンの値を読み込み、その結果を0～255の範囲に変換します。
  0～255の値は出力ピンのPWM(パルス幅変調)値として使います。
  また、シリアルモニターにも結果をプリントします。

  回路:
  - アナログ入力ピン0に、ポテンショメータ(可変抵抗）
    ポテンショメータの中央のピンをアナログ入力ピンに接続する
    ポテンショメータの側面のピン達は、+5V と GND に接続する
  - LED はデジタル出力ピン9と、GNDに接続する（もちろん電流制限抵抗が必要）
*/

// これらの固定値は変更してはいけません。使用するピンに名前を与えています。
const int analogInPin = A0;  // ポテンショメータに接続するアナログ入力ピン
const int analogOutPin = 9; // LEDに接続するアナログ出力ピン

int sensorValue = 0;        // ポテンショメータから読み取った値
int outputValue = 0;        // アナログ出力でＰＷＭ出力する値

void setup() {
  // シリアル通信を初期化する。ボーレートは9600bps
  Serial.begin(9600);
}

void loop() {
  // アナログ入力の値を読む
  sensorValue = analogRead(analogInPin);
  // アナログ入力の0～1024の値を、アナログ出力の使用範囲0～255に変換する
  outputValue = map(sensorValue, 0, 1023, 0, 255);
  // アナログ出力値を変更する
  analogWrite(analogOutPin, outputValue);

  // シリアルモニターに結果をプリントする
  Serial.print("sensor = ");
  Serial.print(sensorValue);
  Serial.print("\t output = ");
  Serial.println(outputValue);

  // アナログ->デジタル変換のために2ミリ秒のウェイトが必要
  delay(2);
}
```

---
それでは、それぞれの関数を見ていきましょう。
前回の **<font color=green>アナログ入力</font>** で解説した部分は割愛します。

まずはグローバル変数とsetup関数です。

```cpp:グローバル変数とsetup関数
// これらの固定値は変更してはいけません。使用するピンに名前を与えています。
const int analogInPin = A0;  // ポテンショメータに接続するアナログ入力ピン
const int analogOutPin = 9; // LEDに接続するアナログ出力ピン

int sensorValue = 0;        // ポテンショメータから読み取った値
int outputValue = 0;        // アナログ出力でＰＷＭ出力する値

void setup() {
  // シリアル通信を初期化する。ボーレートは9600bps
  Serial.begin(9600);
}

```

アナログ出力に使うピン`9`に、int型の固定値(const)`analogOutPin`という名前を付けています。
アナログ出力する値は、int型の変数`outputValue`に格納します。


アナログ出力に使えるピンは、デジタル入出力の中にあります。
![aios_pwm_pin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/331f2bc4-1b55-dd35-6c60-ee103b4c6373.png)
デジタル入出力ピンの中で`~`が付いているピンだけが、アナログ出力に対応しています。

---
次はloop関数を見ていきます。

```cpp:loop関数
void loop() {
  // アナログ入力の値を読む
  sensorValue = analogRead(analogInPin);
  // アナログ入力の0～1024の値を、アナログ出力の使用範囲0～255に変換する
  outputValue = map(sensorValue, 0, 1023, 0, 255);
  // アナログ出力値を変更する
  analogWrite(analogOutPin, outputValue);

  // シリアルモニターに結果をプリントする
  Serial.print("sensor = ");
  Serial.print(sensorValue);
  Serial.print("\t output = ");
  Serial.println(outputValue);

  // アナログ->デジタル変換のために2ミリ秒のウェイトが必要
  delay(2);
}
```
`analogRead`関数で読み取った値が`sensorValue`に入っています。
本当は、読み取った値をそのまま出力したいのですが、

- analogReadの値は0～1023
- analogWriteの値は0～255

で、使える範囲が違います。
そのため、`outputValue`に入れる値は最大255にしなければいけません。

ですが、

```cpp:255以上の値は全部255にする
outputValue = sensorValue;
if( outputValue > 255 ) outputValue = 255;
```
こんなプログラムを書いてしまうと、256～1023の値はすべて255にされてしまいます。
つまり、0-1023の4分の3が255にされてしまい、意味がなくなってしまいます。

では、どんな計算式を書けばよいでしょうか？
答えはこうです。

```cpp:0.0～1.0の割合に変換して、255をかける
float ratio = sensorValue / 1023.0;      // 最大値1023で割ることで、0.0～1.0の割合になる
outputValue = (int)(ratio*255);            // 割合に255をかけることで0.0～255.0になる。最後にfloatはintにする
```
0.0～1.0の割合に変換（これを正規化と呼びます）して、そこに値を掛ければ良いのです。

しかし、Arduinoには便利な関数があります。それが`map`関数です。

`map`関数は、入力された値の上限/下限で正規化し、出力用の上限/下限の範囲に変換します。
解説：[map解説](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2743)

```cpp:sensorValueの0-1023で正規化し、0-255の範囲に収めた値を返す
 outputValue = map(sensorValue, 0, 1023, 0, 255);
```
`map`関数は非常によく使う関数なので覚えておきましょう。

- センサの値を角度に変換する
- センサの値を距離に変換する
- パルスカウントを角度に変換する

などなど、様々な変換計算を簡単にしてくれますよ。

こうして作りだした`outputValue`は、`analogWrite`関数で出力します。
解説：[analogWrite解説](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2153)

前述したように、`analogWrite`に使えるピンはデジタル入出力のうち`~`が付いたピンだけです。
今回は9番ピンを使っています。

こうして、LEDには0～5Vの可変電圧がかかるので、明るさが変化しているのです。

 
　
 
次は、このanalogWriteが中で実際に何をしているのか解説します。

# パルス幅変調（PWM）

工学系出身であれば絶対知っていなければいけない知識のうち、
最も重要といっても過言ではないのが「ＰＷＭ」です。

モータ制御に代表される現代の電子制御には、ＰＷＭはなくてはならない技術です。

ＰＷＭ（パルス幅変調）を簡単に説明すると、
「**<font color=blue>ON/OFFの時間を調整することで、疑似的なアナログ電圧を作り出す</font>**」
となります。
5VのON時間と0VのOFF時間で平均電圧を計算すれば、実質的にアナログな電圧を作ったことと等しくなる、ということです。
下図のような考え方になります。
![aios_pwm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/e5af97c0-33f5-4254-da6c-4fb008448eb6.png)

本当のデジタル->アナログ変換には、それ専用の装置(IC)が必要になるので高価になってしまうのですが、
デジタル出力のON/OFF時間を調整するだけで疑似アナログが作れるのであれば安上がりです。

なので、正確に言えばArduinoの`analogWrite`は「**本当のアナログじゃなくて、なんちゃってアナログ**」です。

「アナログ出力なのに、どうしてデジタル出力ピンから出力するの？」と疑問に思った人はいますか？
「アナログ入力ピン」があるのなら「アナログ出力ピン」が必要なはずだ、と思ったでしょう。
しかし、デジタルを使ったなんちゃってアナログなので、「PWM出力に対応したデジタル出力ピン」を使っているのです。

# まとめ

アナログ出力では、

- analogWrite で出力する
- analogWriteはアナログと書いているが、実はデジタルでON/OFFをする「PWM」という手法
- analogWriteが使えるピンは、`~`が付いているデジタルピンだけ
- analogWriteは0～255までの値が使える
- `map`関数を使うと、範囲(レンジ)の変換が簡単に行える

という事が分かったと思います。


 [目次](https://qiita.com/hsgucci/items/085680123f758c8c3eb4#%E5%AE%9F%E9%A8%93%E6%89%8B%E9%A0%86) へ戻りましょう。次の作業を行ってください。