Arduinoで電圧計を作る:LCD表示とキャリブレーションで0〜25 Vを測定
Arduinoで電圧計を作る方法を解説します。分圧回路による0〜25 Vの測定、抵抗の選び方、ツェナー保護、キャリブレーション、LCD、INA219、電池テスターを紹介します。
Arduinoの電圧計は、一晩で作れる測定器です。2本の抵抗、1つのアナログ入力、数行のコードだけで0〜25 Vを正確に測定し、ディスプレイに表示できます。Arduinoのアナログ入力で読み取れるのは最大5 Vなので、それより高い電圧を測るには、まず分圧回路で電圧を下げ、その後コード内で同じ比率を使って元の値に戻します。バッテリーモニター、電源の電圧表示器、ソーラーパネルのデータロガー、電池テスターは、すべてこの考え方を利用しています。
このガイドでは、測定範囲に合う抵抗値の選び方、ツェナーダイオードと直列抵抗でアナログ入力を過電圧から守る方法、市販の0〜25 V電圧センサーモジュールの仕組み、基準電圧の設定とキャリブレーションで測定値をテスターに近づける方法を順に学びます。続いてI2C LCDに値を表示し、ESP32とRaspberry Pi Picoでも同じ電圧計を作り、INA219で電流測定を加えて電力計にします。最後に、電池の実容量をmAhで測るテスターを作ります。

Arduinoの電圧計はどのように動く?
Arduinoは電圧を直接認識するわけではありません。ADC(アナログ-デジタル変換器)がアナログ入力の電圧を基準電圧と比較し、結果を整数で返します。Arduino UnoのADCは10ビットで、0 Vなら0、基準電圧(初期設定では5 V)なら1023を返します。ピンの電圧はVpin = reading × Vref / 1024です。入力に分圧回路がある場合、実際に測定している電圧はVin = Vpin × (R1 + R2) / R2になります。したがって電圧計全体の精度は、Vrefの実際の値、2本の抵抗の比、ADC自体の誤差という3つの要素で決まります。
| ボード | ADC | 基準電圧 | ピンでの1段階分 | 0〜25 Vの分圧回路での1段階分 |
|---|---|---|---|---|
| Arduino Uno, Nano, Pro Mini | 10ビット(0〜1023) | 5 V(AVcc) | 4.88 mV | 24.4 mV(30 kΩ + 7.5 kΩ) |
| ESP32 | 12ビット(0〜4095) | 約3.3 V(減衰量11 dB) | 約0.8 mV | 約9 mV(100 kΩ + 10 kΩ) |
| Raspberry Pi Pico | 12ビット(read_u16では0〜65535) | 3.3 V(ADC_VREF) | 0.81 mV | 8.9 mV(100 kΩ + 10 kΩ) |
0〜25 V用の分圧回路と抵抗の選び方
測定する電圧は上側の抵抗R1に入り、2本の抵抗がつながる中点をアナログピンに、下側の抵抗R2をGNDにつなぎます。抵抗を選ぶときは、まず最大電圧を決め、その電圧でピンの電圧が基準電圧を少し下回る比率を選びます。Unoなら約4.5 Vが目安です。市販モジュールの30 kΩと7.5 kΩの組み合わせでは、電圧がちょうど5分の1になります。入力が25 Vならピンは5.00 Vです。抵抗値を適当に選んではいけません。合計抵抗が低すぎると測定対象の電源から無駄な電流を消費し、高すぎるとADC内部のサンプリングコンデンサを充電できず、測定値がずれます。ATmega328Pのデータシートは、アナログ入力を駆動する信号源の抵抗を10 kΩ以下にするよう推奨しています。分圧回路では、この信号源抵抗はR1とR2の並列合成抵抗です(30 kΩと7.5 kΩなら6 kΩ)。式の詳しい説明と負荷効果については、分圧回路の記事を参照してください。
| 測定範囲 | Arduino Uno(5 V):R1 + R2 | 範囲上限でのピン電圧 | ESP32 / Pico(3.3 V):R1 + R2 | 範囲上限でのピン電圧 |
|---|---|---|---|---|
| 0〜10 V | 12 kΩ + 10 kΩ | 4.55 V | 33 kΩ + 10 kΩ | 2.33 V |
| 0〜25 V | 30 kΩ + 7.5 kΩ(市販モジュール) | 5.00 V | 100 kΩ + 10 kΩ | 2.27 V |
| 0〜30 V | 56 kΩ + 10 kΩ | 4.55 V | 120 kΩ + 10 kΩ | 2.31 V |
| 0〜50 V | 100 kΩ + 10 kΩ | 4.55 V | 220 kΩ + 10 kΩ | 2.17 V |
保護:ツェナーダイオード、直列抵抗、コンデンサ
分圧回路を正しく計算しても、プローブの当て間違い、電源のスパイク、25 Vのつもりで誤って48 Vに触れるといったことで、ピン電圧が5 Vを超えることがあります。ATmega328Pのピンが耐えられるのは、最大で電源電圧 + 0.5 Vです。ピン内部には5 VとGNDにつながる保護ダイオードがありますが、流せるのは小さな電流だけです。幸い、分圧回路の上側の抵抗がすでに電流を制限しています。R1が30 kΩなら、50 Vに触れてもピンに流れる電流は最大約1.5 mAです。この電流をさらに減らし、余分な電圧を安全に逃がすために、次の保護を追加できます。
- 直列抵抗:分圧回路の中点とアナログピンの間に4.7〜10 kΩの抵抗を入れます。過電圧時にピン内部のダイオードへ流れる電流をマイクロアンペア単位に減らします。
- 100 nFコンデンサ:アナログピンとGNDの間につなぎます。ADCがサンプリング時に必要な電荷をこのコンデンサから得るため、直列抵抗による信号源抵抗の増加を補えます。ノイズや短いスパイクも除去します。
- 5.1 Vツェナーダイオード:1N4733AやBZX55C5V1などのツェナーダイオードを、カソードがピン、アノードがGNDになるようにつなぎます。電圧が5.1 Vを超えようとすると、ツェナーダイオードが導通して余分な電流をGNDへ逃がします。
- ツェナーの漏れ電流に注意:ツェナーダイオードは降伏電圧より少し低い電圧から小さな電流を流し始めます。抵抗値の高い分圧回路では、その電流が測定値を押し下げます。ツェナーダイオードを使う場合はピンの最大電圧を4 V程度に抑えるか、代わりにピンから5 Vラインへショットキーダイオード(BAT85)を入れてください。
- 逆極性:プローブを逆につなぐと、ピンに負の電圧が加わります。直列抵抗で電流を制限できます。安全性を高めるには、アノードをGND、カソードをピンにつないだ2本目のショットキーダイオードを追加できます。
市販の電圧センサーモジュール(0〜25 V)
はんだ付けを避けたい場合は、「電圧センサー」として売られている赤や青の小型モジュールを使えます。中に特別な仕組みはありません。30 kΩと7.5 kΩの抵抗で作った分圧回路、ねじ端子、3ピンヘッダーだけです。比率が5なので、5 VのArduinoでは0〜25 Vを測れますが、3.3 Vボードでは16.5 Vまでです。3.3 Vボードで25 Vに触れるとピンに5 Vがかかって壊れるため、ESP32とPicoでは上の表を参考に分圧回路を自作するほうが安全です。モジュールには保護ダイオードがないので、前の節のツェナーやショットキーダイオードによる対策はここでも必要です。
| モジュールのピン | 機能 | Arduino Uno | ESP32(自作の分圧回路) | Raspberry Pi Pico(自作の分圧回路) |
|---|---|---|---|---|
| VCC(ねじ端子) | 測定対象の電源のプラス極 | 電源 + | R1の上端 | R1の上端 |
| GND(ねじ端子) | 測定対象の電源のマイナス極 | 電源 − | 電源 −とGND | 電源 −とGND |
| S | 分圧された信号 | A0 | GPIO 34(ADC1) | GP26(ADC0、31番ピン) |
| + | ほとんどのモジュールでは未接続 | 接続しません | - | - |
| − | モジュールのGND | GND | GND | GND(例:33番ピン) |
手順で学ぶ:初めてのArduino電圧計
- モジュールのSピンをA0、−ピンをGNDにつなぎます。分圧回路を自作する場合は、電源側に30 kΩ、GND側に7.5 kΩを置き、中点をA0につなぎます。
- ArduinoをUSBにつなぎ、テスターで5VピンとGNDの間の電圧を測り、値を控えます。USB電圧は4.7〜5.1 Vの範囲にあることがよくあります。
- 測定した値を下のコードのVREF定数に入力し、書き込みます。
- 測定したい電源をモジュールのねじ端子につなぎます。プラス極をVCC、マイナス極をGNDに接続してください。最初のテストには9 V電池か可変の実験用電源が最適です。
- シリアルモニタを9600 baudで開きます。電圧を読み取り、同時にテスターでも電源を測定して、2つの値を比較します。
1// Arduino voltmetre: 0-25 V gerilim sensörü modülü / Arduino voltmeter: 0-25 V voltage sensor module
2// Modül S -> A0, modül - -> GND / module S -> A0, module - -> GND
3// Ölçülen kaynak modülün VCC ve GND klemensine / measured source goes to the module's VCC and GND terminals
4const int SENSE_PIN = A0;
5const float VREF = 5.00; // 5V pininde ölçtüğün gerilim / voltage you measured on the 5V pin
6const float R1 = 30000.0; // üst direnç (ohm) / top resistor (ohms)
7const float R2 = 7500.0; // alt direnç (ohm) / bottom resistor (ohms)
8const float CAL = 1.000; // kalibrasyon: gerçek / okunan / calibration: real / measured
9const int SAMPLES = 32; // ortalama için örnek sayısı / samples to average
10
11float readVoltage() {
12 analogRead(SENSE_PIN); // ilk okumayı at / discard the first reading
13 unsigned long sum = 0;
14 for (int i = 0; i < SAMPLES; i++) {
15 sum += analogRead(SENSE_PIN);
16 delayMicroseconds(500);
17 }
18 float adc = (float)sum / SAMPLES; // 0-1023 arası ortalama / average between 0 and 1023
19 float vPin = adc * VREF / 1024.0; // pindeki gerilim / voltage at the pin
20 return vPin * (R1 + R2) / R2 * CAL; // bölücüden önceki gerilim / voltage before the divider
21}
22
23void setup() {
24 Serial.begin(9600);
25}
26
27void loop() {
28 float v = readVoltage();
29 if (v < 0.05) v = 0.0; // boşta gürültüyü sıfırla / zero the noise when idle
30 Serial.print(F("Gerilim / Voltage: "));
31 Serial.print(v, 2);
32 Serial.println(F(" V"));
33 delay(500);
34}コードは毎回32回の測定値を平均するため、ADCの±1段階の揺れがほぼ消え、末尾の桁が安定します。最初の測定値を捨てるのは、ADCのサンプリングコンデンサに前のチャンネルの電荷が残っているためです。0.05 V未満をゼロに丸めることで、何もつないでいないときに0.02〜0.04 Vなどのランダムな値が表示されるのを防ぎます。分解能が24.4 mVであることを忘れないでください。小数点以下2桁まで表示されても、末尾の桁は0.02〜0.03 V刻みで変わります。より細かい分解能が必要なら測定範囲を狭めます。0〜10 Vの分圧回路なら、1段階分は10.7 mVに下がります。
基準電圧とキャリブレーション
Arduinoの電圧計は「測定値が間違っている」とよく言われますが、実際の誤差の原因はたいてい基準電圧です。コードでは5.00 Vなのに、USBからボードに供給される電圧が4.80 Vなら、測定値はすべて4%高くなり、12.00 Vのバッテリーが12.50 Vと表示されます。この電圧はデスクトップとノートパソコンで異なり、USBケーブルによっても変わります。外部ACアダプターでボードに給電すると、レギュレーターが電圧を決めるため、値はまた変わります。解決方法は3つあります。5Vピンを測ってコードに入力する、一定の内部基準電圧を使う、または測定のたびにボード自身の電源電圧を計算し直す方法です。
| 方法 | コード | 利点 | 注意点 |
|---|---|---|---|
| 初期設定(AVcc) | analogReference(DEFAULT) | 設定不要で0〜5 Vの全範囲を使用できます | 電源が変動すると測定値も変わります |
| 内部1.1 V基準電圧 | analogReference(INTERNAL) | 電源に依存せず安定しています | ボードによって1.0〜1.2 Vのばらつきがあります。分圧比を約23にする必要があります(例:220 kΩ + 10 kΩ) |
| 外部基準電圧(AREF) | analogReference(EXTERNAL) | LM4040などの高精度基準電圧源を使うと最も高い精度が得られます | AREFに電圧を加える前にこの行を実行しないと、内部基準電圧源と短絡します |
| 電源電圧の測定(readVcc) | 下のコード | 追加部品が不要で、USBや電池の電圧が変わっても精度を保てます | AVRボード(Uno、Nano、Pro Mini)専用です。1.1 Vの値を一度校正してください |
1// Kendi besleme gerilimini ölçen voltmetre (ATmega328P: Uno, Nano, Pro Mini)
2// Voltmeter that measures its own supply (ATmega328P: Uno, Nano, Pro Mini)
3const int SENSE_PIN = A0;
4const float R1 = 30000.0; // üst direnç / top resistor
5const float R2 = 7500.0; // alt direnç / bottom resistor
6long bandgapMv = 1100; // iç referans (mV), kalibrasyonla düzelt / internal reference (mV), fix by calibration
7
8long readVccMv() {
9 // Referans AVcc, ölçülen kanal iç 1,1 V / reference AVcc, measured channel is the internal 1.1 V
10 ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
11 delay(2); // referans otursun / let the reference settle
12 ADCSRA |= _BV(ADSC); // dönüşümü başlat / start a conversion
13 while (bit_is_set(ADCSRA, ADSC)) {} // bitmesini bekle / wait until it finishes
14 unsigned int raw = ADCW; // 10 bit sonuç / 10-bit result
15 return bandgapMv * 1024L / raw; // Vcc = 1,1 V x 1024 / okunan / Vcc = 1.1 V x 1024 / reading
16}
17
18float readVoltage(float vcc) {
19 analogRead(SENSE_PIN); // kanal değişti, ilk okumayı at / channel changed, discard first
20 unsigned long sum = 0;
21 for (int i = 0; i < 32; i++) sum += analogRead(SENSE_PIN);
22 float vPin = (sum / 32.0) * vcc / 1024.0;
23 return vPin * (R1 + R2) / R2;
24}
25
26void setup() {
27 Serial.begin(9600);
28}
29
30void loop() {
31 float vcc = readVccMv() / 1000.0;
32 float v = readVoltage(vcc);
33 Serial.print(F("Vcc: "));
34 Serial.print(vcc, 3);
35 Serial.print(F(" V Giris / Input: "));
36 Serial.print(v, 2);
37 Serial.println(F(" V"));
38 delay(500);
39}- 内部基準電圧のキャリブレーション:readVccのコードを実行し、シリアルモニタのVcc値と、テスターで測った5Vピンの値を比較します。新しいbandgapMv = 1100 × 実測値 / 表示値です。例えばボードの表示が5.12 V、テスターの測定値が4.98 Vなら、1100 × 4.98 / 5.12 ≈ 1070を入力します。
- 1点キャリブレーション:測定範囲の上限に近い既知の電圧(例:12 V)を接続します。CAL = テスターの値 / Arduinoの値です。この係数は抵抗の誤差による分圧比のずれも補正します。
- 2点キャリブレーション:低電圧で一定のオフセットが見られる場合は、低い電圧(例:2 V)と高い電圧(例:20 V)の2点で測定します。傾きはSlope = (M2 − M1) / (A2 − A1)、オフセットはoffset = M1 − slope × A1です。結果はreal = slope × reading + offsetで求められます(Mはテスターの値、AはArduinoの値)。
- 検証:キャリブレーション後は、測定範囲の中ほどにある3つ目の電圧で結果を確認してください。丁寧に作ったUnoの電圧計は、0〜25 Vを1%未満の誤差で測定できます。
LCD付きのデジタル電圧計

パソコンなしで電圧計を使うには、I2C LCDに値を表示するだけです。LCDのGNDをGND、VCCを5V、SDAをA4、SCLをA5につなぎます。ライブラリマネージャからLiquidCrystal I2Cライブラリ(Frank de Brabander)をインストールしてください。ディスプレイのアドレスは通常0x27で、モジュールによっては0x3Fです。下のコードは、上段に現在の電圧、下段に電源を入れてからの最小値と最大値を表示するため、モーター動作中のバッテリーの電圧低下も確認できます。
1// LCD ekranlı dijital voltmetre (0-25 V, min/maks) / Digital voltmeter with LCD (0-25 V, min/max)
2// LCD: SDA -> A4, SCL -> A5, VCC -> 5V, GND -> GND; sensör S -> A0 / sensor S -> A0
3#include <Wire.h>
4#include <LiquidCrystal_I2C.h>
5
6LiquidCrystal_I2C lcd(0x27, 16, 2); // adres 0x27 ya da 0x3F / address 0x27 or 0x3F
7
8const int SENSE_PIN = A0;
9const float VREF = 5.00; // 5V pininde ölçülen / measured on the 5V pin
10const float RATIO = (30000.0 + 7500.0) / 7500.0; // = 5,0
11const float CAL = 1.000; // kalibrasyon katsayısı / calibration factor
12
13float vMin = 99.99;
14float vMax = 0.0;
15
16float readVoltage() {
17 analogRead(SENSE_PIN);
18 unsigned long sum = 0;
19 for (int i = 0; i < 32; i++) sum += analogRead(SENSE_PIN);
20 float v = (sum / 32.0) * VREF / 1024.0 * RATIO * CAL;
21 return v < 0.05 ? 0.0 : v;
22}
23
24void printField(float value) {
25 char buf[8];
26 dtostrf(value, 5, 2, buf); // sabit genişlik: " 9.87" / fixed width: " 9.87"
27 lcd.print(buf);
28}
29
30void setup() {
31 lcd.init();
32 lcd.backlight();
33 lcd.print(F("Voltmetre 0-25V"));
34 delay(1000);
35 lcd.clear();
36}
37
38void loop() {
39 float v = readVoltage();
40 if (v > 0.5) { // boştayken min/maks güncelleme / don't update min/max when idle
41 if (v < vMin) vMin = v;
42 if (v > vMax) vMax = v;
43 }
44
45 lcd.setCursor(0, 0);
46 lcd.print(F("V: "));
47 printField(v);
48 lcd.print(F(" V "));
49
50 lcd.setCursor(0, 1);
51 lcd.print(F("L"));
52 printField(vMin > 99.0 ? 0.0 : vMin);
53 lcd.print(F(" H"));
54 printField(vMax);
55 lcd.print(F(" "));
56 delay(300);
57}ESP32で電圧計を作る
ESP32は3.3 Vで動作し、ピンには3.6 Vを超える電圧を加えられません。そのため、0〜25 Vの測定には100 kΩ + 10 kΩの分圧回路を使います。入力が25 Vならピンは2.27 Vになります。分圧回路をADC1のピン(GPIO 32〜39)のいずれかにつないでください。ADC2のピンはWi-Fi動作中には使えません。ESP32のADCは12ビットですが、範囲の両端では直線性がありません。0.15 V未満はほとんど検出できず、2.5 Vを超えると誤差が大きくなります。analogReadMilliVolts()は工場でチップに書き込まれた校正データを使い、生の値よりはるかに正確なミリボルト単位の結果を返します。残ったオフセットを取り除くため、コードではGAINとOFFSETの定数を使い、前の節の2点キャリブレーションを適用しています。
1// ESP32 voltmetre: 100k + 10k bölücü ile 0-25 V / ESP32 voltmeter: 0-25 V with a 100k + 10k divider
2// Kaynak + -> 100k -> GPIO 34 -> 10k -> GND, kaynak - -> GND / source + -> 100k -> GPIO 34 -> 10k -> GND
3const int SENSE_PIN = 34; // ADC1_CH6, Wi-Fi açıkken de çalışır / works with Wi-Fi on
4const float RATIO = (100000.0 + 10000.0) / 10000.0; // = 11,0
5const float GAIN = 1.000; // iki noktalı kalibrasyon: eğim / two-point calibration: slope
6const float OFFSET = 0.00; // iki noktalı kalibrasyon: kayma (V) / two-point calibration: offset (V)
7
8float readVoltage() {
9 uint32_t mv = 0;
10 for (int i = 0; i < 64; i++) {
11 mv += analogReadMilliVolts(SENSE_PIN); // fabrika kalibrasyonlu mV / factory-calibrated mV
12 }
13 float vPin = mv / 64.0 / 1000.0; // pindeki gerilim (V) / voltage at the pin (V)
14 float v = vPin * RATIO * GAIN + OFFSET;
15 return v < 0.10 ? 0.0 : v;
16}
17
18void setup() {
19 Serial.begin(115200);
20 analogReadResolution(12); // 0-4095
21}
22
23void loop() {
24 float v = readVoltage();
25 Serial.printf("Gerilim / Voltage: %.2f V\n", v);
26 delay(500);
27}Raspberry Pi Picoで電圧計を作る(MicroPython)
Raspberry Pi Picoのアナログ入力はGP26、GP27、GP28です。ESP32と同じ100 kΩ + 10 kΩの分圧回路をGP26につなぎます。MicroPythonでは、read_u16()が12ビットの結果を0〜65535に拡大して返します。PicoのADC基準電圧は3.3 Vラインをフィルタリングして得ています。精度を高めるには、ADC_VREF(35番ピン)とGNDの間をテスターで測り、値をVREFに入力してください。コードには追加機能もあります。標準のPicoではGP29(ADC3)が内部の分圧回路につながっていて、ボードの電源入力VSYSを3分の1にします。そのため追加部品なしで、Picoに給電している電池の電圧も読み取れます。Pico WとPico 2 WではGP29を無線チップと共用しているため、この行はそのままでは動きません。
1# Raspberry Pi Pico voltmetre (MicroPython) / Raspberry Pi Pico voltmeter (MicroPython)
2# Kaynak + -> 100k -> GP26 -> 10k -> GND / source + -> 100k -> GP26 -> 10k -> GND
3from machine import ADC
4import time
5
6sense = ADC(26) # GP26 = ADC0 (pin 31)
7vsys = ADC(29) # GP29 = VSYS / 3 (yalnız standart Pico / standard Pico only)
8VREF = 3.3 # ADC_VREF; multimetreyle ölçüp düzelt / measure and correct with a multimeter
9RATIO = (100000 + 10000) / 10000 # = 11.0
10GAIN = 1.000 # iki noktalı kalibrasyon: eğim / two-point calibration: slope
11OFFSET = 0.00 # iki noktalı kalibrasyon: kayma (V) / two-point calibration: offset (V)
12
13
14def pin_voltage(adc, samples=64):
15 total = 0
16 for _ in range(samples):
17 total += adc.read_u16() # 0-65535
18 return total / samples * VREF / 65535 # pindeki gerilim / voltage at the pin
19
20
21while True:
22 v_in = pin_voltage(sense) * RATIO * GAIN + OFFSET
23 if v_in < 0.05:
24 v_in = 0.0
25 v_sys = pin_voltage(vsys) * 3 # VSYS = GP29 x 3
26 print("Giris / Input: {:.2f} V VSYS: {:.2f} V".format(v_in, v_sys))
27 time.sleep(0.5)電流測定を加えて電力計に:ACS712またはINA219

電圧計に電流測定を加えると、電力も求められます。式はP = V × Iです。12 Vのバッテリーが1.5 Aを供給すると、電力は18 Wです。電力を時間にわたって積算すると電力量になります。18 Wの負荷を2時間動かすと、36 Whを消費します。よく使われる選択肢は2つあります。ACS712はアナログ出力で、交流電流も測定でき、絶縁も得られます。既存の電圧計に2つ目のアナログチャンネルとして追加するだけです。INA219は同じチップで電圧と電流を12ビット精度で測定し、I2Cで通信する直流電力モニターです。単体で電力計として使えます。
| 項目 | 電圧モジュール + ACS712(5A) | INA219モジュール |
|---|---|---|
| 電圧範囲 | 0〜25 V(分圧回路で決まります) | 0〜26 V(チップ内部) |
| 電流範囲 | ±5 A | ±3.2 A(0.1 Ωのシャント抵抗使用時) |
| 電流の分解能 | Unoでは約26 mA | 0.1 mA |
| 交流電流 | 測定可能 | 測定不可 |
| 接続 | アナログピン2本 | I2C(SDA、SCL)、アドレス0x40 |
| 最適な用途 | モーター、商用電源側の電流測定、絶縁が必要な用途 | 電池、ソーラーパネル、USB機器などの小規模な直流電力測定 |
1// INA219 + I2C LCD ile wattmetre: V, A, W ve Wh / Wattmeter with INA219 + I2C LCD: V, A, W and Wh
2// Kaynak + -> INA219 VIN+ ; INA219 VIN- -> yük + ; yük - -> kaynak - -> Arduino GND
3// Source + -> INA219 VIN+ ; INA219 VIN- -> load + ; load - -> source - -> Arduino GND
4#include <Wire.h>
5#include <Adafruit_INA219.h>
6#include <LiquidCrystal_I2C.h>
7
8Adafruit_INA219 ina219; // varsayılan adres 0x40 / default address 0x40
9LiquidCrystal_I2C lcd(0x27, 16, 2);
10
11float energyWh = 0.0;
12unsigned long lastMs = 0;
13
14void printField(float value, int width, int decimals) {
15 char buf[10];
16 dtostrf(value, width, decimals, buf);
17 lcd.print(buf);
18}
19
20void setup() {
21 Serial.begin(9600);
22 lcd.init();
23 lcd.backlight();
24 if (!ina219.begin()) { // modül bulunamadı / module not found
25 lcd.print(F("INA219 yok!"));
26 while (true) delay(10);
27 }
28 // ina219.setCalibration_16V_400mA(); // küçük akımlar için daha hassas / finer for small currents
29 lastMs = millis();
30}
31
32void loop() {
33 float busV = ina219.getBusVoltage_V(); // VIN- ile GND arası / between VIN- and GND
34 float shuntMv = ina219.getShuntVoltage_mV(); // şönt üzerindeki düşüm / drop across the shunt
35 float loadV = busV + shuntMv / 1000.0; // kaynağın gerilimi / source voltage
36 float currentA = ina219.getCurrent_mA() / 1000.0;
37 float powerW = loadV * currentA;
38
39 unsigned long now = millis();
40 energyWh += powerW * (now - lastMs) / 3600000.0; // W x saat = Wh / W x hours = Wh
41 lastMs = now;
42
43 lcd.setCursor(0, 0);
44 printField(loadV, 5, 2); lcd.print(F("V "));
45 printField(currentA, 6, 3); lcd.print(F("A "));
46 lcd.setCursor(0, 1);
47 printField(powerW, 6, 2); lcd.print(F("W "));
48 printField(energyWh, 6, 2); lcd.print(F("Wh"));
49
50 Serial.print(loadV, 2); Serial.print(F(" V, "));
51 Serial.print(currentA, 3); Serial.print(F(" A, "));
52 Serial.print(powerW, 2); Serial.print(F(" W, "));
53 Serial.print(energyWh, 3); Serial.println(F(" Wh"));
54 delay(500);
55}INA219はVIN+とVIN−の間にある0.1 Ωのシャント抵抗の小さな電圧降下から電流を求めるため、モジュールを電源のプラス側(ハイサイド)に直列に入れます。バス電圧はVIN−とGNDの間で測るため、コードではシャント抵抗の電圧降下を加えて電源の実際の電圧を求めます。初期設定のキャリブレーションは32 V、2 A用です。数百ミリアンペアしか測らない場合は、setCalibration_16V_400mA()の行を有効にして範囲を400 mAに狭めると、小さな電流での精度が上がります。電力量は、各ループで現在の電力と経過時間を掛けた値を積算して求めます。Arduinoの電源を切るとリセットされるため、保持したい場合は定期的に値をEEPROMに書き込んでください。ACS712で同じ計算を行うコードは、ACS712の記事の電力と電力量の節を参照してください。
電池テスター:容量(mAh)を測る電圧計

電池の電圧だけでは、残っているエネルギーを正確には判断できません。劣化した電池でも、満充電なら4.1 Vを示すことがあります。実容量を求める方法は、既知の負荷で放電させ、時間にわたって電流を積算することです。式はmAh = Σ (current × time)です。この回路ではMOSFETが負荷のオン・オフを切り替え、Arduinoが電池電圧と負荷抵抗の下側の電圧を1秒ごとに測ります。2つの電圧の差を負荷抵抗の値で割ると電流が求まります。電池が終止電圧に達するとMOSFETがオフになり、結果が出力されます。開始時には、コードが無負荷時と負荷接続時の電池電圧を比較して内部抵抗も計算します。内部抵抗が高いほど、大電流時の電池の電圧低下が大きくなります。
| 部品 | 接続 | 補足 |
|---|---|---|
| 電池 + | 10 kΩを介してA0へ接続。負荷抵抗の上側にも接続 | A0とGNDの間に100 nFを入れます |
| 4.7 Ωの負荷抵抗 | 電池のプラス極とMOSFETのドレインの間 | 最低5 W(推奨10 W)のセメント抵抗。18650では約0.9 Aが流れ、熱くなります |
| MOSFETのドレイン | 負荷抵抗の下側。10 kΩを介してA1にも接続 | A1とGNDの間にも100 nFを入れます |
| MOSFETのソース | GND | 電池のマイナス極もここにつなぎます |
| MOSFETのゲート | 220 Ωを介してD9へ、100 kΩを介してGNDへ接続 | IRLZ44NなどのロジックレベルMOSFETを使います |
1// Pil kapasite test cihazı: mAh, Wh ve iç direnç / Battery capacity tester: mAh, Wh and internal resistance
2// Pil + -> 10k -> A0 ; pil + -> 4,7 ohm yük -> MOSFET drain ; drain -> 10k -> A1
3// Battery + -> 10k -> A0 ; battery + -> 4.7 ohm load -> MOSFET drain ; drain -> 10k -> A1
4// MOSFET source -> GND ; gate <- D9 (220 ohm), gate -> 100k -> GND ; pil - / battery - -> GND
5const int BAT_PIN = A0;
6const int DRAIN_PIN = A1;
7const int LOAD_PIN = 9;
8const float VREF = 5.00; // 5V pininde ölçülen / measured on the 5V pin
9const float R_LOAD = 4.7; // yük direnci, multimetreyle ölç / load resistor, measure it
10const float CUTOFF_V = 3.00; // Li-ion 3,00 V ; alkalin AA / alkaline AA 0,90 V
11
12float mAh = 0.0;
13float wh = 0.0;
14unsigned long lastMs = 0;
15unsigned long startMs = 0;
16bool running = false;
17
18float readPin(int pin) {
19 analogRead(pin); // kanal değişti, ilk okumayı at / discard first reading
20 unsigned long sum = 0;
21 for (int i = 0; i < 32; i++) sum += analogRead(pin);
22 return (sum / 32.0) * VREF / 1024.0;
23}
24
25float loadCurrent(float vBat) {
26 return (vBat - readPin(DRAIN_PIN)) / R_LOAD; // dirençteki gerilim / R / voltage across R / R
27}
28
29void setup() {
30 Serial.begin(9600);
31 pinMode(LOAD_PIN, OUTPUT);
32 digitalWrite(LOAD_PIN, LOW); // yük kapalı / load off
33 delay(1000);
34
35 float vOpen = readPin(BAT_PIN); // yüksüz gerilim / open-circuit voltage
36 if (vOpen < CUTOFF_V + 0.1) {
37 Serial.println(F("Pil zayif ya da takili degil / Battery low or missing"));
38 return;
39 }
40 digitalWrite(LOAD_PIN, HIGH); // yükü aç / load on
41 delay(1000);
42 float vLoad = readPin(BAT_PIN);
43 float amps = loadCurrent(vLoad);
44 Serial.print(F("Bos / Open: "));
45 Serial.print(vOpen, 3);
46 Serial.print(F(" V Yuklu / Loaded: "));
47 Serial.print(vLoad, 3);
48 Serial.print(F(" V Ic direnc / Internal R: "));
49 Serial.print((vOpen - vLoad) / amps * 1000.0, 0);
50 Serial.println(F(" mOhm"));
51
52 running = true;
53 startMs = lastMs = millis();
54}
55
56void loop() {
57 if (!running) return;
58 unsigned long now = millis();
59 if (now - lastMs < 1000) return; // saniyede bir ölç / measure once per second
60
61 float hours = (now - lastMs) / 3600000.0;
62 lastMs = now;
63 float vBat = readPin(BAT_PIN);
64 float amps = loadCurrent(vBat);
65 mAh += amps * 1000.0 * hours; // akım x süre / current x time
66 wh += vBat * amps * hours;
67
68 Serial.print((now - startMs) / 1000);
69 Serial.print(F(" s "));
70 Serial.print(vBat, 3);
71 Serial.print(F(" V "));
72 Serial.print(amps, 3);
73 Serial.print(F(" A "));
74 Serial.print(mAh, 0);
75 Serial.println(F(" mAh"));
76
77 if (vBat <= CUTOFF_V) { // kesme gerilimi / cut-off voltage
78 digitalWrite(LOAD_PIN, LOW);
79 running = false;
80 Serial.print(F("Bitti / Done: "));
81 Serial.print(mAh, 0);
82 Serial.print(F(" mAh, "));
83 Serial.print(wh, 2);
84 Serial.println(F(" Wh"));
85 }
86}4.7 Ωの負荷では、18650電池の放電電流は約0.9 Aから始まり、0.65 Aに向かって下がります。正常な2500 mAhの電池のテストには約3時間かかります。Li-ionでは終止電圧を絶対に3.0 V未満に設定しないでください。電池が損傷します。単3アルカリ電池をテストする場合は、CUTOFF_Vを0.9 Vに設定します。同じ負荷で約0.3 Aが流れます。結果はラベルの値や電池寿命計算ツールと比較できます。この方法で測った新品の18650の内部抵抗は、ホルダーと配線を含めて通常0.15 Ω未満です。0.3 Ωを超える電池は大電流を必要とする用途には使わないでください。容量の単位については、mAhと電池容量の記事を参照してください。
Arduinoの電圧計を使ったプロジェクトのアイデア
- バッテリーモニターと低電圧アラーム:キャンピングカーやソーラー発電システムの12 Vバッテリーを監視し、11.8 Vを下回ったらブザーを鳴らします。
- ソーラーパネルのデータロガー:パネルの電圧とINA219で測った電流を1分ごとにSDカードに記録し、1日の発電量をWhで求めます。
- 実験用電源の表示器:LM2596モジュールの出力に電圧計とINA219を追加し、実験用電源にします。
- バッテリーの遠隔監視:ESP32で測った電圧をMQTTでHome Assistantに送信したり、電圧が低くなったときにTelegramで通知を受け取ったりします。
- 18650パックのチェック:TP4056で充電したセルの容量をテストし、容量の近いセルを組み合わせて、バランスの取れたパックを作ります。
- 大きな数字の表示:LCDの代わりに7セグメントディスプレイやOLEDディスプレイを使い、部屋の反対側からでも読める大きさで値を表示します。
よくあるミスと対処方法
| 症状 | 考えられる原因 | 対処方法 |
|---|---|---|
| 測定値が常に2〜5%高い、または低い | コードのVREFは5.00 Vですが、ボードは別の電圧で動作しています | 5Vピンを測定してVREFに入力するか、readVccの方法を使います |
| 値が常に飛び跳ねます | 電源のマイナス極をArduinoのGNDにつないでいないか、配線にノイズが乗っています | GNDを共通にし、測定値を平均し、ピンに100 nFを追加します |
| 高い電圧で同じ値のままになります | ピン電圧が基準電圧に達し、ADCが1023で飽和しています | 分圧比を大きくして測定範囲を広げます |
| 上限付近で測定値が曲がり、直線的に変化しません | ツェナーの漏れ電流、またはESP32のADCの上限付近での特性 | ピン電圧を4 V未満(ESP32では2.5 V未満)に抑えるか、ショットキーダイオードを使います |
| ESP32でWi-Fiが始まると測定値が0になるか、変化しなくなります | 分圧回路がADC2のピンにつながっています | ADC1のピンであるGPIO 32〜39のいずれかを使います |
| INA219が負の電流を表示します | VIN+とVIN−が逆です | 電源のプラス極をVIN+、負荷のプラス極をVIN−につなぎます |
| LCDは点灯していますが、文字が出ません | I2Cアドレスかコントラスト設定が間違っています | I2Cスキャナーでアドレスを調べ、背面の半固定抵抗(トリマー)を回します |
Arduinoの電圧計は、測定の仕組みを学ぶのに最適な方法の1つです。2本の抵抗、1つの式、少しのキャリブレーションでテスターに近い結果が得られ、電流センサーを加えれば電力計に、MOSFETを加えれば電池テスターになります。BlueGraysアプリの「LCD付きデジタル電圧計」プロジェクトでは、配線図と用意されたコードを使って同じ回路を手順に沿って作れます。そこからバッテリー監視や電力測定のプロジェクトに進めます。
よくある質問
Arduinoでは何ボルトまで測定できますか?
アナログピンに直接接続できるのは最大5 Vです(ESP32とPicoでは3.3 V)。分圧回路を使えば、この上限を必要に応じて引き上げられます。30 kΩ + 7.5 kΩで25 V、100 kΩ + 10 kΩで50 Vを測定できます。ただし、数十ボルトを超えると安全性と絶縁が重要になり、商用電源の電圧をこの方法で測定してはいけません。
Arduinoの電圧計の測定値がずれるのはなぜですか?
最も多い原因は基準電圧です。コードでは5.00 Vなのに、USBからボードに供給される電圧が4.7〜4.9 Vだと、すべての測定値が同じ比率でずれます。ほかには、誤差5%の抵抗、共通GNDの未接続、抵抗値が高すぎる分圧回路も原因になります。5Vピンを測ってコードに入力するかreadVccの方法を使い、その後既知の電圧でCAL係数を設定すると解決できます。
0〜25 V電圧センサーモジュールをESP32で使えますか?
使えますが、注意が必要です。モジュールの比率は5なので、ESP32で測定できるのは16.5 Vまでです。25 Vに触れるとピンに5 Vがかかり、ESP32が壊れます。ESP32用に100 kΩ + 10 kΩの分圧回路を自作すれば、安全性が高まり、ADCを直線性のよい範囲で使えるため精度も上がります。
Arduinoで230 V ACの商用電源電圧を測定できますか?
分圧回路では絶対に測定しないでください。命に関わる危険があり、Arduinoが商用電源につながってしまいます。商用電源の電圧には、小型トランスを内蔵し、電気的な絶縁を確保できるZMPT101Bなどのモジュールを使ってください。出力は交流なので、コードで実効値(RMS)を計算します。
Arduinoで負の電圧を測定できますか?
直接は測れません。アナログピンはGNDより低い電圧を読み取れず、損傷することがあります。対処方法は、抵抗で信号を5 V側に持ち上げることです。例えば、信号と5 Vの間に同じ値の抵抗を2本入れると、中点では−5 V〜+5 Vが0〜5 Vに変換されます。より高い精度が必要な場合は、オペアンプによるレベル変換回路を使います。
Arduinoの電圧計はテスターと同じくらい正確に測れますか?
校正したUnoの電圧計は、0〜25 Vを1%未満の誤差で測定できます。多くの趣味の工作には十分で、安価なテスターに近い精度です。10ビットADCのため、25 Vレンジでの分解能は約24 mVに制限されます。さらに精度を高めるには、ADS1115などの16ビット外付けADC、誤差0.1%の抵抗、高精度基準電圧源を使えます。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
LDRで光の強さを測定
部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

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

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

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

I2CでLCDディスプレイを使う
Arduinoにディスプレイを接続し、最初のメッセージを表示します。2行のLCDディスプレイは数本の配線だけで接続できます。まず上の行にあいさつを表示し、次に下の行にもう1つのメッセージを表示します。その後、表示を消去して最初から繰り返します。温度や時刻、スコアを表示するプロジェクトは、すべてこのステップから始まります。

ESP32 I2C LCDディスプレイ(16x2)
このプロジェクトでは、16x2文字のLCDをI2CでESP32に接続して文字を表示します。I2Cならデータ用の2本のピン(SDAとSCL)だけで制御でき、従来のパラレル接続のように6本以上のピンを使わずに済みます。コードでは1行目に固定メッセージ、2行目に毎秒増えるカウンターを表示します。また、起動時にI2Cスキャナーを実行し、ディスプレイのアドレス(通常は0x27または0x3F)をシリアルモニタに表示します。

LCDディスプレイを使った距離測定
定規を使わずに距離を測る装置です。センサーの前で手を近づけたり遠ざけたりすると、数値が変わります。HC-SR04が音波を出して反響を捉え、Arduinoが距離をセンチメートル単位で計算し、2行のLCDディスプレイに1秒間に数回表示します。車の駐車センサーの仕組みを、机の上で確かめられます。

ESP32と可変抵抗でLEDの明るさを制御(アナログ→PWM)
可変抵抗を使ってLEDの明るさをアナログ操作で調整します。ESP32のADCピンで可変抵抗からの0-3.3Vの電圧を読み取り、PWM(パルス幅変調)信号に変換してLEDに出力します。可変抵抗を回すと、LEDが徐々に明るくなったり暗くなったりします。Arduino Core 3.0+ではanalogWrite()を直接使えます。ESP32-S2/S3ではADCの分解能を12ビットに固定します。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

