IoT演習第５回、アナログ値の入力とPWM出力

# 1. はじめに
[IoT演習シラバス案その２（授業計画表）](https://qiita.com/takashiyamanoue/items/0cf9d58265bff5883698)の第５週に予定している内容の一部です。

マイクロコントローラ（マイコンと呼ぶこともあります）は、コンピュータ(CPU+メモリ)と入出力機能を、ICの１つのチップや１枚のボードにまとめたものです。マイコンには電源を供給する端子の他に入出力を行う端子がついています。マイコンの入出力端子に、情報を入力するセンサ、情報を出力する表示装置、動作を行うアクチュエータ（モータなど）を接続して、様々な物を制御することができます。

https://ja.wikipedia.org/wiki/マイクロコントローラ

今回は、マイクロコントローラの入出力を行う端子のうち、アナログ値の入力と出力について説明します。内容は以下のとおりです。

- AD変換とは
- Pico Wのアナログ値入力機能の説明
- Pico WのPWM出力機能の説明
- 半固定抵抗の説明
- 演習: 半固定抵抗を回すことによって, LEDを連続的に明るくしたり暗くしたりする回路とプログラムの作成
- 授業の振り返り

# 2. AD変換とは
**AD変換**とは、0か1のような値ではなく、音の大きさや光の強さのような、最大値と最小値の間に細かい量があるような値（**アナログ値**）を、複数桁のデジタル値に変換するものです。本来、アナログ値はその最小値と最大値の間に無限の値がありますし、そもそも最小値と最大値も決まっていません。AD変換の多くは、最大値と最小値を定め、その間の量を、ある定まった有限桁のデジタル値に変換するものです。この時の桁数のことを、**分解能**と呼ぶことがあります。

アナログ量は本来無限桁の値なので、有限桁のデジタル値で表すときに誤差が出ます。これを**量子化誤差**と呼びます。

音のような、その大きさが時間によって変化するものについては、一定周期で繰り返し、AD変換を行い、デジタル値を繰り返し出力します。このときの周期を**標本化周期**（**サンプリング周期**）と呼びます。この周期の逆数を**標本化周波数**（**サンプリング周波数**）と呼びます。**標本化定理**という原理があります。これは、量子化誤差を無視した場合、周期的に変化するアナログ値は、そのアナログ値が持つ最大周波数成分の２倍の周波数でサンプリングすることで、得られたデータから、元の波形を再現できる、というものです。

https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%8A%E3%83%AD%E3%82%B0-%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E5%A4%89%E6%8F%9B%E5%9B%9E%E8%B7%AF


# 3. Pico Wのアナログ値入力機能

Pico W は、AD変換(Analog to Digital Converter, ADC)機能を持っており、AD変換可能なアナログ値入力端子を３つ(31番ピン-GP26-ADC0, 32番ピン-GP27-ADC1, 34番ピン-GP28-ADC2, )持っています（内部のADCを含めると５つ持っています）。この端子に与えられる電圧でアナログ値が入力されます。AD変換によって変換されるデジタル値の桁数は12桁(12bit)です。１つのアナログ値入力端子は、最小値は0Ｖ、最大値は Vref 端子で与えられる電圧（最大3.3V, デフォルトは3.3V）です。様々なアナログデータを、この電圧の範囲に収まる電圧に変換することで、様々なアナログデータを入力することが可能になります。

https://deepbluembedded.com/raspberry-pi-pico-w-pinout-diagram-gpio-guide/

![20240623-pico-w-pinout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/820b1ced-5bff-4298-94b8-4f2445beac3d.png)


(<文字列>は、実際のプログラムの中では、この部分が名前や数字などで置き換えられることを表します)

Pico W の MicroPython において、ADC端子からアナログ値を入力するとき、以下の文を実行します。ここで<adc_pin名>は、ADC入力の機能を表すオブジェクトの変数名です。は、<GPIOのPin番号は>ADC機能を持ったGPIOの端子番号を表します。

```Python
<adc_pin名> = ADC(Pin(<GPIOのPin番号>))
```

入力したアナログ値をプログラム中で得るには、
```Python
<adc_pin名>.read_u16()
```
を実行します。ここでread_u16()は、入力したアナログ値を符号なし16bitで得る関数です。分解能は12bitなので、0〜3.3Vを12bitの解像度で入力し、それを16倍した値が得られることになります。

例えば、
```Python
adc=ADC(Pin(26))
print(adc.read_u16())
```
は、26番のGPIO(ADC0)からアナログ値を入力するオブジェクト adc から、データ(0〜3.3V)を入力（0~3.3Vの範囲の電圧を計測）し、その値を最小が0、最大が65537になるように変換して、それをREPL端末に出力することを表します。


# 4. 可変抵抗/半固定抵抗
Pico W のADC機能が使えることを確認するため、**可変抵抗**または**半固定抵抗**を使ってみることにします。可変抵抗とは、抵抗値を変化させることができる抵抗器です。また、普段は抵抗値を変える必要はないけど、各種調整が必要な時に抵抗値を変化させて調整を行うような用途で使われる可変抵抗を半固定抵抗と呼んでいます。トリマーとかポテンションメータと呼ばれることもあります。

![potmet-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/d10baa1a-2b85-8521-c30c-9dd20e3cfacb.png)



可変抵抗は通常端子を３つ持っていて、そのなかの２つの端子（抵抗端端子、上の図のaとc）の間に細長い抵抗体をつなぎ、のこりの１つの端子（ワイパー端子, 上の図b）の先についているワイパーがその抵抗体のどこかの位置に接触するようになっています。このワイパーの位置をつまみで変化させることによって、ワイパー端子と、残りの端子のどれか１つの端子の間の抵抗が変化します。


https://jp.rs-online.com/web/content/discovery/ideas-and-advice/trimmer-resistors-guide

https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%86%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%A1%E3%83%BC%E3%82%BF

# 5. 半固定抵抗によって得られるアナログ値を入力して、その値を出力する

半固定抵抗でPicoWのアナログ入力端子にかける電圧を変化させて、その電圧に応じたデジタル値(16bit)をREPL端末に出力する回路とプログラムを作ってみます。

## 5.1 回路

第４回までに作成したスイッチのON/OFFでPicoWの外に付けたLEDを点滅させる回路に、半固定抵抗（50KΩ=50,000Ω)を下の図のように追加します(半固定抵抗R2以外の発光ダイオードやスイッチに関する配線や抵抗ははずしてかまいません)。

半固定抵抗の３つの端子のうち、２つの抵抗端端子のどちらか１つをGND、もう一つをPicoWの3.3V出力の端子に接続しています。ワイパー端子を、ADC0の入力端子に接続しています。これにより、ワイパーが抵抗のGND側にあると0Vに近い電圧、ワイパーが抵抗の3.3V側にあると3.3Vに近い電圧、ワイパーが抵抗の中央付近にあると、1.6V強の電圧が、ADC０の入力端子にかかることになります。なお、このとき、Pico WのADC0とGND間の抵抗が、半固定抵抗の抵抗値より十分大きい、と仮定しています。


![20240715-led-dw-pot-bb-schimatic-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/8ba4ad87-50f7-0332-b01b-aed44b8a35a5.png)

## 5.2 プログラム

「3. Pico Wのアナログ値入力機能の説明」で紹介した、Micro Pythonの、ADCクラスとそのmethodを使って、ADC0の入力電圧の高低に従って、REPL端末に値を出力するプログラムを次のように書くことができます。

```Python
from machine import ADC, Pin
import time

adc = ADC(Pin(26))

while True:
    print(adc.read_u16())
    time.sleep(1)
```

## 5.3 実行例

(以下は、[第２回](https://qiita.com/takashiyamanoue/items/4baba8e23c86dc07962d)で説明している、Anaconda(Python), VSCode, VSCodeのMicroPicoのExtentionがインストール済みであることを前提としています。)


MicroPico の拡張機能をインストールしたVSCode のエディタに5.2のプログラムを入力し、Pico WをUSBケーブルで接続し、画面下のRunをクリックして、このプログラムを実行します。実行が始まると、現在の反固定抵抗のワイパー端子に得られる電圧に対応した16bitの値が表示されます。反固定抵抗のつまみを左右に回して、この電圧を変化させると、その電圧に対応した16bitの値が変化することがわかります。

![20240715-adc-run-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/0540676f-0ac9-94f8-7924-fc9d9714714f.png)


# 6. Pico WのPWM出力機能の説明

Pico Wは、ADCは持っていますが、デジタル値からアナログ値へ変換するDAコンバータ(**DAC**)は持っていません。その代わり、Pico WのGPIOは、**PWM**(Pulse Width Modulation, ***パルス変調***)の機能を持っています。

PWMとは、一定周期(逆数をとると一定周波数)でHigh(1,On)とLow(0,Off)の繰り返し（***パルス***）を出力するのですが、そのとき、Highのときの時間の比率と、Lowの時の時間の比率(***デューティー比***)を変化させるものです。デューティー比を変化させるとHighの時の長さの平均値が変化することになり、これにより、変化するアナログ値を出力することになります。また、この出力電圧を抵抗とコンデンサを使った回路に入れると、デューティー比に比例した電圧に近い値を得ることができます（Highの時間が長いと電圧が高くなり、短いと電圧が低くなる）。この回路をローパスフィルタと呼びます。


![pwm-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/c7edf80f-bc06-8189-61b1-ea51319d52a2.png)


https://en.wikipedia.org/wiki/Pulse-width_modulation

また、PWMはサーボモータの制御信号としてよく用いられています。サーボモータとは、ラジコンカーやラジコン飛行機の進行方向の制御などに用いられている、モータの軸の回転角度を信号で変化させることができるモーターです。

https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%BC%E3%83%9C%E3%83%A2%E3%83%BC%E3%82%BF

Pico WのMicro Python でPWMを利用する場合、
```Python
<PWM変数名>=PWM(Pin(<GPIOピン番号>),freq=<周波数>,duty_u16=<デフォルトのデューティー比>)
```
を実行し、GPIOピン番号で指定した端子にPWM出力を割り当てます。freqとduty_u16は省略することが可能で、その場合はデフォルトの値となります。

```Python
<PWM変数名>.freq(<新周波数>)
```
を実行すると、PWMの周波数が新周波数になります。

```Python
<PWM変数名>.duty_u16(<デューティー比>)
```
を実行すると、そのデューティー比のPWM信号が、<GPIOピン番号>の端子から出力されます。
Highの時の電圧は3.3VでLowの時の電圧は0Vです。

https://micropython-docs-ja.readthedocs.io/ja/latest/rp2/quickref.html#pwm-pulse-width-modulation

例えば、IoT演習第4回の、外部に発光ダイオードとタクトスイッチを接続して、タクトスイッチを押すと、発光ダイオードが点灯する回路をそのまま使って、REPL端末で

```Python
from machine import Pin, PWM
pwm=PWM(Pin(15))
pwm.freq(1000)
pwm.duty_u16(10000)
```
を実行すると、発光ダイオードが少し暗く光ります。10000を大きくすると明るくなり、小さくすると暗くなります。ここでは、pwm.freq(1000)を実行することにより、pwmの周波数を1000Hz (1KHz)にしています。

# 7. 演習: 半固定抵抗を回すことによって, LEDを連続的に明るくしたり暗くしたりする回路とプログラムの作成

「5.反固定抵抗によって得られるアナログ値を入力して、その値を出力する」プログラムと、「6. Pico WのPWM出力機能の説明」を参考にして、反固定抵抗を回すことによって、発光ダイオードの明るさが連続的に変化するプログラムを作成してみてください。回路は、5.の回路をそのまま使ってください（発光ダイオードとそれに直列につながっていた抵抗を外していた場合は元に戻してください）。

## 7.1 解答例　(回路)

「5.反固定抵抗によって得られるアナログ値を入力して、その値を出力する」の時の回路と同じ。

## 7.2 解答例　(プログラム)
```Python
from machine import Pin, PWM, ADC

pwm = PWM(Pin(15))
adc = ADC(Pin(26))

pwm.freq(1000)

while True:
	duty = adc.read_u16()
	pwm.duty_u16(duty)
```