基礎 · 読了まで22分

ADCとは? Arduinoで学ぶアナログ-デジタル変換

ADCとは何か、どのように動くのかを解説します。アナログ信号とデジタル信号の違い、10ビットと12ビットの分解能、analogReference、電圧への換算式、ESP32のADC2、ノイズの減らし方まで紹介します。

ADC(アナログ-デジタル変換器)は、ピンに加わる連続的に変化する電圧を、マイコンが扱える数値に変換する回路です。ArduinoでanalogRead()を呼び出すたびに、裏でこの変換器が働いています。Arduino Unoの10ビットADCは0〜5 Vの電圧を0〜1023の数値に、ESP32とRaspberry Pi Picoの12ビットADCはおよそ0〜3.3 Vを0〜4095の数値に変換します。ポテンショメーター、LDR、土壌水分センサー、ジョイスティック、温度センサーなど、アナログ出力のものを読み取るための鍵になるのがADCです。

このガイドでは、アナログ信号とデジタル信号の違い、ADCが電圧をステップごとに数値へ変換するしくみ、そして分解能と基準電圧が測定にどう影響するかを学びます。読み取り値を電圧に換算する式、analogReferenceによる測定範囲の変更、サンプリングレート、ノイズを減らす実践的な方法を、実際の数値を使って見ていきます。さらに、両端で直線にならないESP32のADC、Wi-FiとADC2の競合、減衰(attenuation)の設定、MicroPythonでのRaspberry Pi PicoのADCの使い方も取り上げ、最後に逆の働きをするDACを簡単に紹介します。

ポテンショメーターをADCで読み取る
ADCを試すいちばん簡単な方法はポテンショメーターです。つまみを回すと、中央のピンの電圧が0 Vから電源電圧まで変化します。

アナログ信号とデジタル信号の違いは?

アナログ信号は、ある範囲の中で無限の値をとることができ、時間とともに途切れなく変化する量です。ポテンショメーターの中央のピンの電圧が0 Vから5 Vへ変わるとき、1.2 V、1.21 V、1.215 Vのように、その間のあらゆる値を通ります。マイクが生み出す音の波、温度で変わるサーミスタの電圧、ゆっくり下がっていく電池の電圧もアナログです。一方、デジタル信号は決まったレベルしか持ちません。Arduinoのロジックの世界では、そのレベルはLOW(0)とHIGH(1)の2つです。Unoのデジタル入力は約1.5 Vより低いとLOW、3.0 Vより高いとHIGHとみなし、その間は不定の領域です。そのためdigitalRead()は電池の3.7 Vと4.2 Vの違いを見分けられず、どちらもHIGHと判断します。デジタル信号の強みはノイズに強いことで、4.6 Vでも5.0 Vでも同じHIGHを意味します。アナログの値を測ったり、保存したり、計算に使ったりするには、まずそれを数値、つまりデジタルの値に変換する必要があります。その役割を担う部品がADCです。

ADCとは? どのように動くのか

ADCの仕事は2つのステップにまとめられます。1つ目はサンプリングです。変換器は測定の瞬間に入力電圧を小さなコンデンサ(サンプル・ホールド回路)に写し取り、変換が終わるまで一定に保ちます。ATmega328Pではこのコンデンサは約14 pFです。2つ目は量子化です。保持した電圧を、基準電圧を等しいステップに分けた目盛りの中でいちばん近いステップに丸め、そのステップの番号を結果として返します。Arduino Uno、ESP32、PicoのADCはすべて逐次比較型(SAR、Successive Approximation Register)です。SAR ADCは数当てゲームのように動きます。まず基準電圧の半分を試し、入力のほうが高ければ最上位ビットを1に、低ければ0のままにします。次に残りの範囲の半分を試し、これをビットごとに繰り返します。こうして10ビットのADCは、わずか10回の比較で1024通りの中から正しいステップを見つけます。下の表では、わかりやすくするために、基準電圧5 Vの4ビットADCが3.2 Vの入力をどう測るかを示しています。

ステップ試すビット比較電圧入力(3.2 V)のほうが高い?結果のビット
1ビット3(重み8)8 / 16 × 5 = 2.500 Vはい → 11000
2ビット2(重み4)12 / 16 × 5 = 3.750 Vいいえ → 01000
3ビット1(重み2)10 / 16 × 5 = 3.125 Vはい → 11010
4ビット0(重み1)11 / 16 × 5 = 3.4375 Vいいえ → 01010
結果全ビット完了1010 = 10 → 10 × 5 / 16 = 3.125 V誤差0.075 V(1ステップは0.3125 V)1010

結果に出た0.075 Vの差が量子化誤差で、どのADCもこれからは逃れられません。実際の電圧が2つのステップの間にあっても、ADCはそのどちらか一方しか答えられないからです。誤差は1ステップ(LSB)未満に収まるので、ステップが小さいほど細かく測れます。また、サンプル・ホールド用のコンデンサは測定のたびに充電される必要があるため、ADCにつながる信号源の抵抗も重要です。ATmega328Pのデータシートでは、信号源の出力インピーダンスを約10 kΩ以下にすることを推奨しています。抵抗値の大きい分圧回路ではコンデンサを時間内に充電できず、読み取り値がずれてしまいます。マイコンに入っているSAR型以外にも、ADCにはほかの種類があります。シグマデルタ型のADCは遅いものの非常に高精度で、はかりのプロジェクトで使うHX711は24ビット、I2Cで接続するADS1115モジュールは16ビットの分解能があります。フラッシュ型のADCはステップごとに専用のコンパレーターを使います。非常に高速で、オシロスコープなどに使われています。

ADCの分解能:10ビットと12ビットとは?

分解能は、ADCが測定範囲をいくつのステップに分けるかを表します。NビットのADCは2^N個のステップを持ちます。10ビットなら1024ステップ(0〜1023)、12ビットなら4096ステップ(0〜4095)、16ビットなら65,536ステップです。1ステップあたりの電圧をLSB(最下位ビット)と呼び、LSB = Vref / 2^Nで求めます。Arduino Unoでは5 V / 1024 = 4.88 mV、ESP32とPicoでは3.3 V / 4096 = 0.81 mVです。つまりUnoは4.88 mVより小さな変化を見分けられません。10 mV/°Cを出力するLM35なら、約0.5 °C刻みということになります。ただし、分解能と精度は同じではありません。ADCのノイズ、非線形性、基準電圧の誤差によって、カタログ上のビットの一部は役に立たなくなります。これを表す指標が有効ビット数(ENOB)です。Raspberry Pi PicoのRP2040のADCは12ビットの結果を返しますが、データシートによると有効ビット数は8.7です。3つのボードのADCを並べて比べてみましょう。

項目Arduino Uno R3ESP32 (WROOM-32)Raspberry Pi Pico
ADCの分解能10ビット(0〜1023)12ビット(0〜4095)12ビット(MicroPythonでは0〜65535に換算)
アナログ入力6本(A0〜A5、NanoではA0〜A7)18チャンネル(ADC1に8、ADC2に10)3ピン(GP26〜GP28)+VSYS/3(GP29)+温度センサー
初期設定の測定範囲0〜5 V約0〜3.1 V(11 dBの減衰)0〜3.3 V(ADC_VREF)
1ステップ(LSB)4.88 mV約0.8 mV0.81 mV
基準電圧の選択肢DEFAULT(5 V)、INTERNAL(1.1 V)、EXTERNAL(AREF)の3種類減衰:0、2.5、6、11 dBADC_VREFピンに外部基準電圧
サンプリングレート約10,000回/秒連続(DMA)モードで20 kHz〜2 MHzハードウェアで500,000回/秒
精度についての注意絶対誤差±2 LSB両端で非線形、キャリブレーションが必要ENOB 8.7ビット、約30 mVのオフセット
アナログ出力(DAC)なし(PWMを使用)2チャンネル、8ビット(GPIO 25と26)なし(PWMを使用)

ADCの読み取り値を電圧に換算する式

ADCが返す数値はそれだけでは意味を持ちません。電圧に換算するには、基準電圧とステップ数を使います。電圧 = ADCの値 × Vref / 2^Nです。Arduino UnoではV = 値 × 5.0 / 1024となり、analogRead()が512を返したなら、ピンの電圧は512 × 5 / 1024 = 2.50 Vです。逆に、ある電圧がどの数値になるかは値 = V × 1024 / Vrefで求められます。3.3 VはUnoでは約675と読み取られます。多くの例では1024ではなく1023で割っています。ATmega328PのデータシートにはADC = Vin × 1024 / Vrefという式が載っており、最大値の1023はVrefより1ステップ低い電圧を表します。1023で割ると最大値がちょうど5.00 Vと表示されますが、すべての値が約0.1%ずれます。この差はADC自体の±2 LSBの誤差より小さいので、どちらでも問題ありません。大切なのはプロジェクトの中で統一することです。本当に大きな誤差の原因はVrefそのものです。USBから給電したUnoの5Vピンは4.7〜5.1 Vくらいのことが多く、式に5.0と書くと最大5%の誤差が出ます。そのため精密な測定では、5Vピンをテスターで測って、実際の値をコードに書いてください。ESP8266の唯一のアナログ入力(A0)も10ビットです。チップ自体は0〜1 Vを測りますが、NodeMCUやWemos D1 Miniなどのボードでは内蔵の分圧回路によって測定範囲が約0〜3.2 Vに広がっています。

ボードと設定読み取り範囲電圧への換算例
Arduino Uno / Nano(DEFAULT、5 V)0〜1023V = 値 × 5.0 / 1024768 → 3.75 V
Uno、analogReference(INTERNAL)の場合0〜1023V = 値 × 1.1 / 1024512 → 0.55 V
ESP32(11 dB)0〜4095キャリブレーション済み:analogReadMilliVolts() / 1000、概算:値 × 3.3 / 40951650 mV → 1.65 V
Raspberry Pi Pico(read_u16)0〜65535V = 値 × 3.3 / 6553532768 → 1.65 V
ESP8266 NodeMCU / Wemos D1 Mini(A0)0〜1023V = 値 × 3.2 / 1024512 → 1.60 V

ポテンショメーターの配線:Arduino、ESP32、Pico

ポテンショメーターのピンArduino UnoESP32Raspberry Pi Pico
端のピン15V3V33V3(OUT)、ピン36
中央のピン(信号)A0GPIO 34 (ADC1_CH6)GP26 / ADC0、ピン31
端のピン2GNDGNDAGND(ピン33)またはGND
ノイズ対策のコンデンサ(任意)A0とGNDの間に100 nFGPIO 34とGNDの間に100 nFGP26とAGNDの間に100 nF
ESP32とRaspberry Pi Picoのアナログ入力に加えられるのは最大3.3 Vです。ポテンショメーターの端のピンを5 Vにつないだり、5 V出力のセンサーを直接つないだりすると、ピンが永久に壊れることがあります。Unoでも、入力に電源電圧(5 V)より高い電圧や0 Vより低い電圧を加えないでください。それより高い電圧は分圧回路で下げます。何もつながっていない、浮いたままのアナログピンはランダムな値を読み取ります。これは故障ではなく、ピンが「宙に浮いた」状態になっているだけです。

ステップごとに:Arduinoで初めてのADC読み取り

  1. 回路を組む:10 kΩのポテンショメーターの端のピンを5VとGNDに、中央のピンをA0につなぎます。
  2. 5Vピンを測る:テスターで5VとGNDの間の電圧を測り、コードの定数VREFに書き込みます(例:4.93)。
  3. コードを書き込む:下のスケッチをArduino IDEで開き、ボードとポートを選んで書き込みます。
  4. シリアルモニタを開く:9600ボーに設定し、つまみを回すとADCの値が0〜1023の間で変わる様子を確認します(詳しくはシリアルモニタのガイドを参照)。
  5. 確認する:テスターでA0とGNDの間を測り、画面の電圧と比べます。差は10〜20 mV程度に収まるはずです。
C++ (Arduino IDE)
1// Potansiyometreyi ADC ile okuma / Reading a potentiometer with the ADC
2// Kenar bacaklar 5V ve GND'ye, orta bacak A0'a / Outer legs to 5V and GND, wiper to A0
3const int POT_PIN = A0;
4const float VREF = 5.0;      // 5V pininde ölçtüğün gerilim / voltage you measured on the 5V pin
5
6void setup() {
7  Serial.begin(9600);
8}
9
10void loop() {
11  int raw = analogRead(POT_PIN);             // 0-1023 arası sayı / number between 0-1023
12  float volts = raw * VREF / 1024.0;         // sayıyı volta çevir / convert the number to volts
13  int percent = map(raw, 0, 1023, 0, 100);   // yüzdeye ölçekle / scale to percent
14
15  Serial.print(F("ADC: "));
16  Serial.print(raw);
17  Serial.print(F("  Gerilim / Voltage: "));
18  Serial.print(volts, 3);
19  Serial.print(F(" V  Yuzde / Percent: "));
20  Serial.println(percent);
21  delay(200);
22}

analogRead()は約100 µsで測定を終え、0〜1023の整数を返します。コードではこの数値をまず式で電圧に、次にmap()でパーセントに変換しています。map()は整数演算なので小数部分は切り捨てられます。小数の結果が必要なら、floatを使って自分で計算してください。これはレシオメトリック測定です。ポテンショメーターとADCが同じ5 Vから給電されているので、電源が4.9 Vに下がっても、つまみの位置、つまりADCの値は変わりません。変わるのは計算した電圧だけです。LDRやNTCのように分圧回路で読み取るセンサーも同じです。一方、電池の電圧や、LM35のように決まったmVを出力するセンサーを測るときは、Vrefの実際の値がそのまま結果に影響します。

基準電圧とanalogReference

基準電圧(Vref)はADCの目盛りの最上点です。入力電圧がVrefと同じかそれを超えると、結果は最大値(1023)に張り付きます。Vrefを下げると測定範囲は狭くなりますが、1ステップが小さくなるので小さな信号を細かく測れます。Arduino UnoではanalogReference()関数で基準電圧を選びます。INTERNALを選ぶとVrefは1.1 Vになり、1ステップは1.07 mVまで小さくなります。10 mV/°CのLM35なら約0.1 °C刻みで読み取れますが、その代わり1.1 V、つまり110 °Cを超える値は測れません。内部の1.1 V基準電圧自体もチップごとに1.0〜1.2 Vの範囲でばらつくので、精密なプロジェクトではテスターで一度キャリブレーションする必要があります。基準電圧を切り替えた直後の数回の読み取りは不正確なことがあり、Arduinoのドキュメントにもそう書かれています。切り替えたあとに数回分の読み取りを捨てるのはよい習慣です。また、基準電圧の設定はボードのすべてのアナログピンに同時に影響します。選べる設定はボードによって異なります。

オプション基準電圧ボード使う場面
DEFAULT5 V(3.3 Vのボードでは3.3 V)Uno・Nano・Mega・Leonardoポテンショメーター、LDR、ジョイスティックなど、電源電圧に対してレシオメトリックなセンサー
INTERNAL1.1 V(ATmega328P)、Leonardoでは2.56 VUno・Nano・Leonardo(Megaにはなし)LM35のような低電圧のセンサー
INTERNAL1V1 / INTERNAL2V561.1 V / 2.56 VMegaのみMegaでの小さな信号
EXTERNALAREFピンの電圧(0〜5 V)Uno・Nano・Mega自前の高精度基準電圧(例:2.5 Vや4.096 V)を使う
AR_INTERNAL約1.43 V(チップの内部基準電圧)Uno R4 MinimaとWiFiUno R4での小さな信号
AREFピンに外部の基準電圧をつないだ場合は、analogRead()を呼び出す前にanalogReference(EXTERNAL)を書く必要があります。そうしないと、チップ内部で作られる基準電圧とAREFピンの電圧がショートし、マイコンが壊れることがあります。基準電圧を切り替えて使う場合は、外部の電圧を5 kΩの抵抗を通してAREFにつなぎます。ピン内部の約32 kΩの抵抗と分圧回路を作るので、AREFの電圧が少し下がる(2.5 Vなら約2.2 V)ことを計算に入れてください。AREFには0 Vより低い電圧や5 Vより高い電圧を絶対に加えないでください。

サンプリングレート:ADCは1秒に何回測れる?

サンプリングレートとは、ADCが1秒間に何回測定できるかを表すものです。Arduino Unoでは、16 MHzを128で割ってADCのクロックを125 kHzにしており、1回の変換に13クロックかかります。そのためanalogRead()には約100 µsかかり、1秒あたり最大10,000回程度の測定ができます。温度、明るさ、電池の電圧のようにゆっくり変わる量にはこれで十分すぎるほどで、1秒に10回でも足りることがほとんどです。速い信号ではナイキストのルールが関係してきます。信号を失わずにとらえるには、その最高周波数の少なくとも2倍の速さでサンプリングする必要があり、実際には5〜10倍が好まれます。50 Hz(西日本では60 Hz)の電源ハムを見るには少なくとも100回/秒、4 kHzまでの話し声を記録するには少なくとも8,000回/秒が必要です。Unoでもっと速く測りたい場合は、ADCのプリスケーラを小さくできます。setup()にADCSRA = (ADCSRA & 0xF8) | 0x04;と書くとプリスケーラが16になり、ADCのクロックが1 MHzに上がって、analogRead()は約16 µsまで短くなります。その代わり、精度がいくらか下がります。ESP32では、analogRead()を1回ずつ呼び出しても1秒に数万回の測定ができ、DMAを使う連続モード(ArduinoではanalogContinuous())なら20 kHz〜2 MHzの範囲で速度を選べます。このモードはADC1のピンでしか使えません。Raspberry Pi PicoのADCは独立した48 MHzのクロックで動き、1回の変換を96クロック、つまり2 µsで終え、1秒あたり500,000回に達します。MicroPythonのループではこの速さに追いつけないので、最高速度で使うにはC SDKとDMAが必要です。

ESP32のADC:非線形性、ADC2とWi-Fiの競合、減衰

ESP32の12ビットADCは、カタログ上はUnoより4倍細かく測れますが、実際には注意すべき点が3つあります。1つ目は非線形性です。ESP32のADCは0 V付近では約100〜150 mVまで0を返し、上の端では入力が3.3 Vに届く前に4095で飽和します。その間の曲線も完全な直線ではありません。2つ目は基準電圧のばらつきです。ESP32の内部基準電圧は設計上1100 mVですが、Espressifによるとチップごとに1000〜1200 mVの範囲でばらつきます。これはキャリブレーションなしの読み取りで約±9%の誤差になるということです。そのためEspressifは、製造時にキャリブレーションデータをチップのeFuseに書き込んでいます。ArduinoのanalogReadMilliVolts()関数はこのデータを使って生の値をキャリブレーション済みのミリボルトに直接変換するので、たいていの場合はanalogRead() × 3.3 / 4095で計算するよりずっと正確です。3つ目は減衰(attenuation)です。ADCのコアが測れるのは約1 Vまでで、それより高い電圧は入力部にある調整可能な減衰器で小さくされます。Arduinoの初期設定は11 dBで、約3.1 Vまで読み取れますが、この設定でEspressifが推奨する正確な測定範囲は150〜2450 mVです。小さな信号では、減衰を下げると精度と分解能の両方が上がります。

減衰Arduinoの定数正確に測れる範囲用途
0 dBADC_0db100〜950 mV小さな信号、最も細かい測定
2.5 dBADC_2_5db100〜1250 mV1 V前後のセンサー出力
6 dBADC_6db150〜1750 mV中程度の信号
11 dB(初期設定)ADC_11db150〜2450 mV(読み取りは約3.1 Vまで可能)ポテンショメーターや0〜3.3 Vのセンサー
ESP32では、ADC2をWi-Fiドライバも使います。Wi-Fiを開始したあとは、ADC2のピン(GPIO 0、2、4、12、13、14、15、25、26、27)から読み取れません。この場合ESP-IDFはタイムアウトエラー(ESP_ERR_TIMEOUT)を返し、Arduinoコアの最新バージョンではanalogRead()が0を返します。Wi-Fiを使うプロジェクトでは、アナログセンサーを必ずADC1のピンにつないでください。GPIO 32、33、34、35、36(VP)、39(VN)です。このうち34〜39は入力専用で内部プルアップ抵抗がないため、アナログの読み取りに最適です。なお、GPIO 0、2、12、15は起動時に使われるストラッピングピンでもあるので、アナログセンサーにはまったく向いていません。
C++ (Arduino IDE)
1// ESP32 ADC: ham değer ve kalibre milivolt / ESP32 ADC: raw value and calibrated millivolts
2// Potansiyometre: 3V3, GND ve orta bacak GPIO 34 / Pot: 3V3, GND and wiper to GPIO 34
3const int POT_PIN = 34;      // ADC1_CH6, Wi-Fi açıkken de çalışır / ADC1_CH6, works with Wi-Fi on
4
5void setup() {
6  Serial.begin(115200);
7  analogReadResolution(12);                     // 0-4095 (varsayılan / default)
8  analogSetPinAttenuation(POT_PIN, ADC_11db);   // yaklaşık 3,1 V'a kadar / up to about 3.1 V
9}
10
11void loop() {
12  uint32_t rawSum = 0;
13  uint32_t mvSum = 0;
14  for (int i = 0; i < 16; i++) {                // 16 ölçümün ortalaması / average of 16 readings
15    rawSum += analogRead(POT_PIN);              // ham ADC değeri / raw ADC value
16    mvSum += analogReadMilliVolts(POT_PIN);     // eFuse kalibrasyonlu mV / eFuse-calibrated mV
17  }
18  float raw = rawSum / 16.0;
19  float naiveV = raw * 3.3 / 4095.0;            // basit formül / naive formula
20  float calibratedV = mvSum / 16.0 / 1000.0;    // kalibre edilmiş / calibrated
21
22  Serial.printf("Ham / Raw: %4.0f  Formul / Formula: %.3f V  Kalibre / Calibrated: %.3f V\n",
23                raw, naiveV, calibratedV);
24  delay(500);
25}

このコードは2つの結果を並べて表示します。簡単な式で生の値を換算したものと、analogReadMilliVolts()がキャリブレーションした値です。ポテンショメーターを両端に向けて回すほど差が大きくなり、いちばん下で式が0.000 Vを示すところでも、テスターではまだ0.1 Vくらいを測っていることに気づくでしょう。analogSetPinAttenuation()は1本のピンだけの減衰を、analogSetAttenuation()はすべてのピンの減衰を変更します。ESP32はUnoよりはっきりとノイズが多く、特にWi-Fiの送信中は顕著です。Espressif自身も、入力に100 nFのコンデンサを入れ、複数回のサンプルを平均することを勧めており、コードの16回平均がそれにあたります。ESP32-S3やESP32-C3などの新しいチップでは、より高度なカーブフィッティングによるキャリブレーションが使われますが、ピン番号やADC2のルールはモデルによって違うので、必ずボードのピン配置を確認してください(「ピン配置」ツール)。3.3 Vより高い電圧を測るための分圧回路の作り方は、分圧回路のガイドで解説しています。

Raspberry Pi PicoのADC(MicroPython)

Raspberry Pi Picoには外部から使えるアナログ入力が3つあります。GP26(ADC0)、GP27(ADC1)、GP28(ADC2)です。GP29(ADC3)はボード上でVSYS電源電圧の3分の1を測り、ADC4はチップ内部の温度センサーにつながっています。MicroPythonのread_u16()関数は12ビットの結果を0〜65535の範囲に換算するので、同じコードを異なるボードで使えますが、実際の分解能は4096ステップのままです。RP2040には独立した基準電圧がなく、ADC自身の電源を基準にしています。Picoでは、この電源(ADC_VREF、ピン35)は3.3 Vのスイッチングレギュレーター(SMPS)の出力を約200 Ωの抵抗でフィルタリングして作られています。Raspberry Piのボードのデータシートには、その結果が3つはっきり書かれています。3.3 Vの精度には限りがあること、電源に多少ノイズがあること、そしてADCが引き込む電流によって約30 mVのオフセットが生じることです。対策も同じ資料にあります。GP23をHIGHにしてレギュレーターをリップルの少ないPWMモードにすること、使っていないADCチャンネルをAGNDにつないで、その読み取り値をオフセットとして差し引くこと、そしてずっと高い精度が必要ならADC_VREFとAGNDの間にLM4040のような3.0 Vの基準電圧ICをつなぐこと(この場合、測定範囲は0〜3.0 Vになります)です。また、RP2040のADCには512、1536、2560、3584の値で小さな段差(DNL誤差)があることが知られています。複数回の読み取りを平均するとその影響を減らせます。Pico WではGP23が無線チップ用に予約されていて、レギュレーターのモードはWL_GPIO1で制御されるので、Pico Wでは下のコードのGP23の行を削除してください。

MicroPython
1from machine import ADC, Pin
2import time
3
4pot = ADC(26)                 # GP26 = ADC0 (pin 31)
5chip_temp = ADC(4)            # dahili sıcaklık sensörü / internal temperature sensor
6VREF = 3.3                    # ADC_VREF; multimetreyle ölçüp düzelt / measure and correct with a multimeter
7
8# Yalnız Pico ve Pico 2 (W değil): SMPS PWM moduna, daha az gürültü / Plain Pico and Pico 2 only (not W): SMPS to PWM mode, less noise
9smps_mode = Pin(23, Pin.OUT)
10smps_mode.value(1)
11
12
13def read_average(adc, samples=16):
14    total = 0
15    for _ in range(samples):
16        total += adc.read_u16()           # 0-65535 (12 bit ölçeklenmiş / 12-bit scaled)
17    return total / samples
18
19
20while True:
21    raw = read_average(pot)
22    volts = raw * VREF / 65535            # sayıyı volta çevir / convert the number to volts
23    t_volts = read_average(chip_temp) * VREF / 65535
24    temp_c = 27 - (t_volts - 0.706) / 0.001721    # RP2040 veri sayfası formülü / RP2040 datasheet formula
25    print("ADC: {:5d}  {:.3f} V  Yonga / Chip: {:.1f} C".format(int(raw), volts, temp_c))
26    time.sleep(0.5)

ADCのノイズを減らす方法

一定の電圧を測っていても、ADCの値が1〜2ステップほど揺れるのは普通のことです。量子化、電源ラインのリップル、長い配線が拾う干渉、近くのモーターやWi-Fiの送信などがその原因になります。Unoでは±1〜2ステップの揺れは正常とみなされ、それよりずっと大きければ、たいてい回路に問題があります。ノイズにはハードウェアとソフトウェアの両方で対処できます。

  • コンデンサを入れる:アナログピンとGNDの間に100 nFのセラミックコンデンサを入れると、速い干渉を取り除けるうえ、抵抗の大きい信号源でもサンプル・ホールド用のコンデンサにすぐ電荷を供給できます。
  • 配線を短くする:センサーのケーブルが長い場合は、信号線とGND線をより合わせます。モーターやリレーの配線はアナログの配線から離して通します。
  • グラウンドを正しく取る:モーター、リレー、LEDテープなど大電流の負荷の戻り電流が、センサーのGND線を流れないようにします。グラウンドは1か所でまとめます(一点アース)。
  • きれいな基準電圧を使う:USB給電はノイズが多いので、電圧の絶対値を測る場合は内部基準電圧か外部の基準電圧ICを使うと安定します。
  • チャンネルを切り替えたら最初の読み取りを捨てる:ADC入力のマルチプレクサには前のチャンネルの電荷が残ることがあります。複数のピンを順に読むときは、各ピンで1回分の読み取りを捨ててください。
  • 平均する:N回の読み取りを平均すると、ランダムなノイズは約√N分の1になります。16回なら4分の1です。
  • オーバーサンプリング:4^n回の読み取りを合計して結果をnビット右にシフトすると、分解能がnビット増えます。16回の合計を2ビットシフトすれば、Unoの10ビットが12ビットになります。これが効くには、信号に少なくとも1ステップ分の自然なノイズが含まれている必要があります。
  • メディアンフィルタと指数移動平均:メディアンフィルタはモーターによる急なスパイクを取り除き、指数移動平均(EMA)はごくわずかなメモリでなめらかな曲線を作ります。
C++ (Arduino IDE)
1// Ham, ortalama ve EMA filtreli ADC değerleri / Raw, averaged and EMA-filtered ADC values
2// Serial Plotter ile izle / Watch with the Serial Plotter
3const int SENSOR_PIN = A0;
4const float ALPHA = 0.1;     // EMA katsayısı (0-1) / EMA coefficient (0-1)
5float ema = 0;
6
7void setup() {
8  Serial.begin(115200);
9  analogRead(SENSOR_PIN);              // ilk okumayı at / discard the first reading
10  ema = analogRead(SENSOR_PIN);        // filtreyi başlat / seed the filter
11}
12
13void loop() {
14  int raw = analogRead(SENSOR_PIN);    // ham değer / raw value
15
16  long sum = 0;
17  for (int i = 0; i < 16; i++) {       // 16 ölçüm topla / sum 16 readings
18    sum += analogRead(SENSOR_PIN);
19  }
20  float average = sum / 16.0;          // ortalama (0-1023) / average (0-1023)
21
22  ema += ALPHA * (raw - ema);          // üstel hareketli ortalama / exponential moving average
23
24  Serial.print(F("ham:"));
25  Serial.print(raw);
26  Serial.print(F(" ortalama:"));
27  Serial.print(average);
28  Serial.print(F(" ema:"));
29  Serial.println(ema);
30  delay(20);
31}

コードを書き込んでArduino IDEのシリアルプロッタを開くと、3本の線が見えます。生の値はガタガタし、16回の平均ははっきり落ち着き、EMAはいちばんなめらかですが変化への反応が少し遅れる曲線です。ALPHA係数は0〜1の間で選びます。0.1は、新しい読み取りが結果に10%ずつ反映される、つまりおよそ直近10回分の読み取りが効くフィルタという意味です。値を大きくすると反応は速くなりますが、ノイズも増えます。どのフィルタを選ぶかは信号次第です。ゆっくり変わる温度には強めのEMA、すばやい反応が必要なジョイスティックには短い平均が向いています。移動平均、メディアンフィルタ、ヒステリシスは、センサーデータのフィルタリングのガイドで実際のセンサーの例を使って比べています。

DACの簡単な紹介:デジタルからアナログへ

DAC(デジタル-アナログ変換器)はADCの逆の働きをします。数値を受け取り、それに比例した電圧を作り出します。8ビットのDACに0を書けば0 V、255を書けば電源電圧に近い値、128を書けばその約半分が得られます。いちばん簡単なDACは、R-2Rと呼ばれる抵抗のはしご(ラダー)で作られます。ここでよくある勘違いをはっきりさせておきましょう。Arduino UnoのanalogWrite()はDACではなく、PWMを出力します。ピンは実際には0 Vと5 Vの間を高速にオン・オフしています。LEDの明るさやモーターの速度にはこれで十分ですが、本当に一定の電圧が必要なら、PWM出力にRCローパスフィルタを追加する必要があります。フィルタの時定数は「RC時定数」ツールで計算できます。本物のDACを持つボードとしては、ESP32のGPIO 25とGPIO 26に8ビットのDACが2つあり、dacWrite(ピン, 値)で使います。一方、ESP32-S3とESP32-C3にはDACがありません。Arduino Uno R4にはA0ピンに12ビットのDACがあります。Uno R3、Nano、Picoでは、I2Cで接続する12ビットのMCP4725モジュールがいちばん手軽です。DACを使えば音を出したり、正弦波や三角波を出力したりでき、DACの出力をADCの入力につないで、既知の電圧でADCをテストすることもできます。

C++ (Arduino IDE)
1// ESP32 DAC ile sinüs dalgası / Sine wave with the ESP32 DAC
2// Çıkış GPIO 25'te, osiloskopla izle / Output on GPIO 25, watch it with an oscilloscope
3const int DAC_PIN = 25;      // DAC1 = GPIO 25 (DAC2 = GPIO 26)
4const int STEPS = 64;        // bir periyottaki nokta sayısı / points per period
5uint8_t sineTable[STEPS];
6
7void setup() {
8  for (int i = 0; i < STEPS; i++) {
9    sineTable[i] = 128 + 127 * sin(2 * PI * i / STEPS);  // 1-255 arası tablo / table between 1-255
10  }
11}
12
13void loop() {
14  for (int i = 0; i < STEPS; i++) {
15    dacWrite(DAC_PIN, sineTable[i]);   // 0 → 0 V, 255 → yaklaşık 3,3 V / 0 → 0 V, 255 → about 3.3 V
16    delayMicroseconds(150);            // yaklaşık 100 Hz / roughly 100 Hz
17  }
18}

ADCを使ったプロジェクトのアイデア

  • デジタル電圧計:分圧回路で0〜25 Vを測り、結果をLCDに表示します。手順はArduinoで電圧計を作るのガイドにあります。
  • 自動常夜灯:LDRの電圧を読み取り、暗くなったらLEDを点灯します。
  • スマート植木鉢:土壌水分センサーのアナログ出力を使い、土が乾いたらポンプを動かします。
  • ジョイスティックで操作するロボット:ジョイスティックの2つの軸は別々のアナログ入力です。その値をモーターの速度と向きに変換します。
  • 電流計:ACS712の電流に比例した電圧を読み取り、機器がどれだけ電流を消費しているかを調べます。
  • 音量メーター:KY-038サウンドセンサーのアナログ出力をすばやくサンプリングし、ピーク値に応じてLEDバーを点灯します。
  • 電池残量計:電池で動くプロジェクトで電圧を測り、残りの充電量をパーセントでディスプレイに表示します。

よくある間違いと対処法

症状考えられる原因対処法
何もつないでいないのに値がランダムに変わるピンが浮いている(フローティング状態)センサーをつなぐ。使わないピンは読まないか、GNDに落とす
値が1023(ESP32では4095)に張り付く入力が基準電圧を超えている、またはINTERNALが選ばれている基準電圧または減衰を調整し、高い電圧は分圧回路で下げる
ESP32でWi-FiをオンにするとanalogRead()が0を返すセンサーがADC2のピンにつながっているGPIO 32〜39のADC1のピンを使う
ESP32で小さな電圧が0、大きな電圧が4095になるADCが両端で直線になっていないanalogReadMilliVolts()を使い、信号を150〜2450 mVの範囲に収める
計算した電圧がテスターと3〜5%違うVrefが5.00 Vではない(USB給電)5Vピンを測ってVREFに書くか、キャリブレーション係数を加える
2本のピンを順に読むと値が互いに影響するマルチプレクサと信号源の高い抵抗切り替え後の最初の読み取りを捨て、信号源を10 kΩ未満にし、100 nFを入れる
モーターやリレーが動くと値が跳ねる共通のGNDラインのノイズ、電源電圧の低下負荷のGNDを別の配線で戻し、コンデンサとメディアンフィルタを使う
AREFに電圧を加えたあと読み取りがおかしくなったanalogReference(EXTERNAL)を呼ばずにAREFを使った先にEXTERNALを選ぶか、5 kΩの抵抗を通してAREFにつなぐ
INTERNALを選んだあと、ポテンショメーターが途中で1023になる1.1 Vの基準電圧はすべてのアナログピンに影響するレシオメトリックなセンサーを読む前にDEFAULTに戻し、数回分の読み取りを捨てる
Picoで入力をGNDにしても0より大きい値になるADCのオフセットとADC_VREFのノイズ1つのチャンネルをAGNDにつないでオフセットを測って差し引き、GP23をHIGHにする
ADCの回路を確かめるいちばん早い方法は、既知の電圧を測ることです。まずアナログ入力をGNDにつなぎ、0に近い値になることを確認します。次に、テスターで電圧を測っておいた単3電池のマイナスをGNDに、プラスをアナログピンにつなぎます。約1.5 Vなら、Unoでは307前後、ESP32のanalogReadMilliVolts()では1500 mV前後、Picoのread_u16()では29,800前後になるはずです。結果が合えば、コードと配線は正しいということです。合わない場合、原因はおそらく基準電圧か共通のGNDにあります。

ADCは物理の世界とコードをつなぐ橋です。分解能、基準電圧、サンプリングレートがわかれば、画面に出る数値が何を表していて、どこまで信頼できるかもわかります。Unoでは基準電圧を正しく測り、ESP32ではADC1のピンとanalogReadMilliVolts()を使い、Picoではオフセットとノイズを考慮に入れます。そのうえで平均とフィルタで結果を落ち着かせましょう。BlueGraysアプリには、LDRで光の強さを測定、LCDディスプレイ付きデジタル電圧計、NTCでの温度測定、ポテンショメーターによるサーボモーターやLEDの明るさの制御など、アナログの読み取りを使うプロジェクトが、回路図とコード付きでステップごとに用意されています。

よくある質問

ADCとは何ですか? 何に使いますか?

ADC(アナログ-デジタル変換器)は、ピンの電圧をマイコンが処理できる数値に変換する回路です。ポテンショメーター、LDR、温度センサーや湿度センサーなど、アナログ出力の部品を読み取るのに使います。ArduinoではanalogRead()命令がこの変換器を動かします。

アナログ信号とデジタル信号の違いは何ですか?

アナログ信号はある範囲のどんな値もとることができ、途切れなく変化します。一方、デジタル信号は決まったレベルだけでできていて、ArduinoではLOWとHIGHです。digitalRead()はピンが0か1かだけを教えてくれますが、analogRead()は電圧がどれだけあるかを数値で返します。

ArduinoのanalogReadはなぜ0〜1023の値を返すのですか?

Arduino UnoのADCは10ビットで、2^10 = 1024通りのステップを持ち、0から1023までの番号が付いています。0は0 Vを、1023は基準電圧(通常は5 V)より1ステップ低い電圧を表します。ESP32とPicoの12ビットADCは0〜4095の値を返します。

ADCの値はどうやって電圧に換算しますか?

電圧 = ADCの値 × 基準電圧 / ステップ数の式を使います。Arduino UnoではV = 値 × 5.0 / 1024です。ESP32ではanalogReadMilliVolts()がキャリブレーション済みのミリボルトを直接返します。Picoでは、MicroPythonのread_u16()の値にV = 値 × 3.3 / 65535を使います。

ESP32のADC2のピンはWi-Fiと一緒に使えますか?

使えません。ESP32ではWi-FiドライバもADC2ユニットを使うため、Wi-Fiがオンの間はADC2のピン(GPIO 0、2、4、12〜15、25〜27)から読み取れません。Wi-Fiを使うプロジェクトでは、アナログセンサーをADC1のピン(GPIO 32〜39)につないでください。

DACとは何ですか? ADCとどう違いますか?

DAC(デジタル-アナログ変換器)はADCの逆の働きをし、数値を電圧に変換します。ADCはセンサーからの電圧を測るために、DACは音や信号などの電圧を作るために使います。Arduino UnoにはDACがなく、analogWrite()はPWMを出力します。ESP32にはGPIO 25と26に8ビットのDACが2つ、Uno R4にはA0に12ビットのDACがあります。

全プロジェクトを見る
LDRで光の強さを測定
#11初級

LDRで光の強さを測定

部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

4.6(81)
139
LCDディスプレイ付きデジタル電圧計
#56中級
プレミアム

LCDディスプレイ付きデジタル電圧計

自作の測定器で、電池や電源アダプターが出力する電圧を調べます。2つの抵抗で作る分圧回路が、高い電圧をボードで読み取れるレベルまで下げます。Arduinoが元の電圧を計算し、2行のLCDディスプレイにボルト単位で表示します。この回路では約55ボルトまでの直流電圧を測定できます。コンセントの商用電源には絶対に接続しないでください。

4.5(27)
36
ESP32とLDRモジュールで光の強さを読み取る
#113初級

ESP32とLDRモジュールで光の強さを読み取る

LDRモジュールの生のADC値と、暗い状態・明るい状態の基準値から計算した相対的な明るさの割合を、シリアルモニタでリアルタイムに確認します。この割合はルクスの測定値ではありません。モジュールの校正の仕組みを学べます。しきい値の決定に使うため、プロジェクト10の前に取り組むのが適しています。

4.6(36)
55
ESP32と可変抵抗でLEDの明るさを制御(アナログ→PWM)
#115中級
プレミアム

ESP32と可変抵抗でLEDの明るさを制御(アナログ→PWM)

可変抵抗を使ってLEDの明るさをアナログ操作で調整します。ESP32のADCピンで可変抵抗からの0-3.3Vの電圧を読み取り、PWM(パルス幅変調)信号に変換してLEDに出力します。可変抵抗を回すと、LEDが徐々に明るくなったり暗くなったりします。Arduino Core 3.0+ではanalogWrite()を直接使えます。ESP32-S2/S3ではADCの分解能を12ビットに固定します。

4.5(19)
32
NTCと2x16 LCDディスプレイで温度を測定
#44初級
プレミアム

NTCと2x16 LCDディスプレイで温度を測定

小さなビーズのようなセンサーで周囲の温度を測ります。NTCというこの部品は、温まるほど電気が流れやすくなります。Arduinoがその変化を読み取り、数式で摂氏温度に変換して、2行のLCDディスプレイに0.5秒ごとに表示します。手のひらでセンサーを温めて、数値が上がる様子を見るのも楽しみの1つです。

4.5(33)
44
ポテンショメーターでサーボモーターを操作
#36初級
プレミアム

ポテンショメーターでサーボモーターを操作

ポテンショメーターのつまみを回すと、サーボモーターのアームもそれに合わせて回ります。つまみの一方の端が0度、もう一方の端が半回転に対応します。Arduinoがつまみの位置を常に読み取り、サーボモーターが動きにすぐ反応します。ロボットアームや回転式カメラ台などのプロジェクトへの第一歩です。

4.6(37)
52
ジョイスティックでLEDを制御
#59中級
プレミアム

ジョイスティックでLEDを制御

ジョイスティックのスティックをいずれかの方向へいっぱいに倒すと、その方向のLEDが点灯します。右、左、上、下にそれぞれ異なる4色のLEDがあります。スティックを離すと中央に戻り、すべてのLEDが消灯します。ロボットカーや画面上のゲームを操作するための第一歩です。

4.7(50)
67
ESP32の調色ライト(可変抵抗3個 + RGB LED + OLED)
#138上級
プレミアム

ESP32の調色ライト(可変抵抗3個 + RGB LED + OLED)

ESP32と3個の可変抵抗で、好みの色を作れるスマート調色ライトです。赤・緑・青の可変抵抗を回すと、カソードコモンのRGB LEDの色がリアルタイムに変わります。SSD1306 OLEDディスプレイには、各色の0~255の値、色の強さを示すバー、#FF8040などのWeb用HEXカラーコードをリアルタイムで表示します。小さなプロ仕様のカラースタジオとして、RGBの色の仕組み、ADCによるアナログ値の読み取り、PWMによる明るさの制御、I2Cディスプレイの表示設計を1つのプロジェクトで学べます。

4.7(6)
10