Arduinoで太陽追尾装置を作る:4つのLDRとサーボの2軸タイプ
Arduinoで太陽追尾装置を作ります。1軸と2軸の違い、4つのLDRで光の方向を見つける方法、サーボ制御、震えを防ぐ不感帯、固定のソーラーパネルとの比較実験を解説します。
Arduinoの太陽追尾装置は、ソーラーパネルを1日中太陽に向け続ける自動の仕組みです。センサーが光の来る方向を測り、モーターがパネルをそちらへ回します。一般的なArduinoの作品では、4つのLDR(光で抵抗が変わるセンサー)と2つのサーボモーターを使います。LDRがどちらの側が明るいかをボードに伝え、サーボがパネルをその方向へ回します。固定のパネルは朝と夕方に太陽を横から見ることになり、得られるはずのエネルギーの大部分を逃しますが、追尾するパネルは1日中どの時間でもほぼ直角に近い角度で光を受けられます。
このガイドでは、1軸と2軸の追尾の違い、4つのLDRで光の差を計算する方法、サーボが無駄に震えるのを止める不感帯(許容範囲)の考え方、パネルを支える機構をステップごとに学びます。Arduino Uno、ESP32、Raspberry Pi Pico用のすぐコンパイルできるコードに加え、追尾するパネルを固定のパネルと比べてエネルギーの増加を実際に測る実験、部品リスト、科学コンテストでプロジェクトを発表するためのヒントも紹介します。

太陽追尾装置とは?なぜ発電量が増えるのか
パネルが生み出す電力は、光が表面に当たる角度で決まります。光が表面に垂直に当たるとパネルは最も多くの光を受け、斜めに当たるほど同じ光線がより広い面積に広がり、パネルが受ける強さは入射角のコサインだけ下がります。そのため、垂直から60°ずれた光は、真正面から当たる光の半分の電力しか生みません。下の表はこの損失を示しています。よく見ると、小さな誤差はほとんど影響しないこともわかります。太陽から5°ずれたパネルが失う電力はわずか0.4%です。この事実は、あとで不感帯を決めるときに必要になります。
| 入射角(垂直からのずれ) | cos(角度) | パネルが受ける電力 |
|---|---|---|
| 0° | 1.000 | 100% |
| 5° | 0.996 | 99.6% |
| 10° | 0.985 | 98.5% |
| 20° | 0.940 | 94% |
| 30° | 0.866 | 87% |
| 45° | 0.707 | 71% |
| 60° | 0.500 | 50% |
| 75° | 0.259 | 26% |
空の太陽の位置は2つの角度で表します。地平線に沿ってどの方向にあるかを示す方位角と、地平線からどれだけ高いかを示す高度です。地球は1時間に15°回るので、太陽は空を平均して約4分に1°移動し、高度は季節によって大きく変わります。北緯約39°(ワシントンD.C.、リスボン、トルコ中部のおよその緯度で、日本では岩手県や秋田県のあたり)では、値は次のようになります。
| 日付 | 正午の高度 | 日の出の方向 | 日の入りの方向 | 昼の長さ |
|---|---|---|---|---|
| 6月21日 | 約74° | 約59°(北東) | 約301°(北西) | 約15時間 |
| 3月21日と9月23日 | 約51° | 90°(真東) | 270°(真西) | 約12時間 |
| 12月21日 | 約28° | 約121°(南東) | 約239°(南西) | 約9.5時間 |
方位角は北から時計回りに測ります。表から2つの重要な結論が導けます。夏の太陽は北東から昇って北西に沈むので、1日に約240°移動します。180°回るサーボでは、日の出と日の入りの前後の時間には太陽に完全には向けませんが、その時間の太陽は低く弱いので損失は小さくて済みます。冬の太陽は正午でも低いままなので、南向きの固定パネルには季節ごとに違う傾きが必要です。追尾装置は両方の問題を自分で解決します。
1軸か2軸か?
| 項目 | 1軸 | 2軸 |
|---|---|---|
| 動き | 東西のみ | 東西 + 傾き(上下) |
| LDRの数 | 2 | 4 |
| サーボの数 | 1 | 2 |
| 固定パネルに対する増加(大規模なシステム) | 約15〜35% | 約30〜45% |
| 機構 | シンプルで頑丈 | パンチルトブラケット、部品が多い |
| コード | 差が1つ、判断が1つ | 差が2つ、2軸を同時に |
| 向いている人・用途 | 最初の作品、中学生 | 高校生、科学コンテスト、効率の比較 |
1軸の追尾装置は、太陽の東から西への動きだけを追います。傾きは固定で、季節に合った角度に設定します。太陽の1日の最大の動きは東から西なので、この軸で増加分の大半が得られます。大規模な太陽光発電所でも、1軸の追尾が最も一般的です。2軸のシステムは少し多くのエネルギーを集めますが、費用がかかり、保守も多く必要です。ただし学校のプロジェクトとしては、2軸のほうが見栄えのするデモになり、2つの軸を同時に制御することを学べる絶好の課題です。
光の差を測る:4つのLDRと影の壁
LDRが1つだけでは周囲の明るさしかわかりません。方向を見つけるには、少なくとも2つのLDRを比べる必要があります。2軸の追尾装置では、4つのLDRを2×2の正方形に並べ、その間に光を通さない十字(+)形の仕切り、つまり影の壁を置きます。センサーが太陽にまっすぐ向いていると、壁はどのLDRにも影を落とさず、4つとも同じ光を受けます。太陽が片側に動くと、壁が反対側のLDRに影を落とし、その値が下がります。壁の高さが感度を決めます。高さ3 cmの壁は、5°ずれると幅約2.6 mmの影を落とします(3 cm × tan 5°)。これで5 mmのLDRの半分が覆われます。高い壁は感度が高いものの範囲の狭いセンサーに、低い壁は粗いものの範囲の広いセンサーになります。LDRの仕組みを詳しく知るにはLDR光センサーを読んでください。
- 上の平均:(左上 + 右上)/ 2
- 下の平均:(左下 + 右下)/ 2
- 左の平均:(左上 + 左下)/ 2
- 右の平均:(右上 + 右下)/ 2
- 縦の差(dV):上 − 下。プラスならパネルを上に、マイナスなら下に傾けます。
- 横の差(dH):左 − 右。プラスならパネルを左に、マイナスなら右に回します。
センサーで追尾するか、時計で追尾するか?
LDRでの追尾が唯一の方法ではありません。太陽の位置は日付、時刻、場所から前もって計算できるので、光センサーをまったく使わないシステムもあります。3つの方法を比べてみましょう。
| 方法 | 仕組み | 長所 | 短所 |
|---|---|---|---|
| センサー方式(LDR、閉ループ) | 光の差を測り、明るい側へ向く | シンプル。場所や時刻が不要。機構の誤差を自分で補正する | 雲の下では不安定。反射やセンサーの汚れの影響を受ける |
| 時計方式(天文計算、開ループ) | 日付、時刻、場所から太陽の位置を計算する | 雲の下でも正しい方向を向く。動きを前もって計画できる | 時刻、場所、取り付けの向きを正確に設定する必要がある。機構の誤差に気づけない |
| ハイブリッド | 計算で近づき、LDRで微調整する | 両方の方法の長所を合わせ持つ | コードが複雑で、RTCモジュールが追加で必要 |
このガイドではセンサー方式を作ります。場所や時刻のデータが不要で、準備が簡単で、機構の誤差を自分で補正できるからです。プロジェクトをさらに発展させたいなら、あとでRTCモジュールを使った時計方式の追尾を加えて2つの方法を比べると、審査員の前でとても説得力のある追加の実験になります。
部品リスト
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino Uno(またはNano) | 1 | ESP32やRaspberry Pi Picoでも可 |
| LDR(GL5528、5 mm) | 4 | 1軸なら2個 |
| 10 kΩの抵抗 | 6 | LDRの分圧回路用に4本、パネルの分圧回路用に2本。屋外ではLDR用を1〜2.2 kΩに |
| 22 kΩの抵抗 | 2 | パネルの分圧回路用 |
| 33 Ω・5 Wのセメント抵抗 | 2 | パネルごとに負荷抵抗を1本 |
| SG90またはMG90Sのサーボ | 2 | 1軸なら1個。風の強い場所ではMG90S |
| パンチルト用のサーボブラケット | 1 | 既製のプラスチックのキットか3Dプリント |
| ミニソーラーパネル6 V 1 W(110×60 mm) | 2 | 1枚は追尾用、1枚は固定の基準用 |
| 470 µF 16 Vのコンデンサ | 1 | サーボの電源と並列に |
| 5 V 2 AのACアダプター、または単3充電池(ニッケル水素)4本 | 1 | サーボ用の外部電源 |
| ブレッドボードとジャンパーワイヤー | 1セット | 常設にするならユニバーサル基板 |
| 厚紙、黒いテープ、または3Dプリントの仕切り | 1 | 十字形の影の壁 |




配線:Arduino Uno、ESP32、Pico
各LDRの片方の足は電源(Unoでは5V、ESP32とPicoでは3V3)へ、もう片方の足はアナログピンへつなぎます。同じアナログピンからGNDへ固定抵抗をつなぎます。こうすると、光が増えるほど値が上がります。パネル側では、各パネルの2つの端子の間に負荷抵抗(33 Ω)をつなぎます。パネルの電圧は、22 kΩと10 kΩの分圧回路で3.2分の1にしてからアナログピンに入れます。パネルのプラス端子からアナログピンへ22 kΩ、アナログピンからGNDへ10 kΩです。パネルのマイナス端子もボードのGNDにつなぐ必要があります。共通グランドがないと、測定は意味をなしません。
| 接続 | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| 左上のLDR | A0 | GPIO 32 | GP26(1軸では東のLDR) |
| 右上のLDR | A1 | GPIO 33 | GP27(1軸では西のLDR) |
| 左下のLDR | A2 | GPIO 34 | なし(外付けのADCが必要) |
| 右下のLDR | A3 | GPIO 35 | なし(外付けのADCが必要) |
| 追尾パネル(分圧回路の中点) | A4 | GPIO 36(VP) | GP28 |
| 固定パネル(分圧回路の中点) | A5 | GPIO 39(VN) | なし |
| 横のサーボの信号 | D9 | GPIO 18 | GP15 |
| 縦のサーボの信号 | D10 | GPIO 19 | なし(1軸) |
| LDRの分圧回路の電源 | 5V | 3V3 | 3V3 |
| サーボの電源 | 外部の5 V | 外部の5 V | 外部の5 V |
| すべてのGND(パネルのマイナスを含む) | GND | GND | GND |
機構:パンチルトブラケットとパネルのバランス
最も簡単な機構は、2つのSG90サーボを重ねる既製のパンチルトブラケットです。下のサーボがパネルを横(東西)に回し、上のサーボが縦に傾けます。センサーのブロックは、その面がパネルの表面と完全に平行になるようにパネルの縁に取り付けます。センサーがパネルと違う方向を向いていると、システムは太陽を完璧に追尾しているのに、パネルは常に数度ずれた方向を見ることになります。110×60 mmの6 V 1 Wのミニパネルの重さは約22 gで、軸から3 cmの位置なら約0.07 kg·cmのトルクになり、SG90の1.8 kg·cmを大きく下回ります。本当の負荷は風です。屋外でテストするなら、パネルをサーボの軸の中心に置き、金属ギアのMG90Sを使い、大きなパネルにはMG996Rのような強力なサーボに移ってください。回転のためにケーブルにはゆとりのある輪を残します。張ったケーブルはサーボに負担をかけ、接続を引き抜いてしまいます。サーボの基本はサーボモーターとは?を読んでください。
ステップごとの組み立て
- 4つのLDRをユニバーサル基板の切れ端に2×2の正方形にはんだ付けし、その間に高さ2〜3 cmの十字形の厚紙か3Dプリントの仕切りを接着します。つや消しの黒に塗るか、黒いテープで覆います。
- 各LDRの分圧回路を作ります。LDRを電源からアナログピンへ、固定抵抗をアナログピンからGNDへつなぎます。
- サーボをパンチルトブラケットに取り付け、両方を90°にして、中央の位置でホーンをねじ止めします。
- 上のブラケットに、パネルとセンサーのブロックを面が平行になるように固定します。
- パネルの測定回路を作ります。パネルの端子の間に負荷抵抗を、パネルのプラス端子とGNDの間に分圧回路を入れ、中点をA4につなぎます。
- サーボを外部の5 V電源から給電し、GNDをすべてつなぎ、470 µFのコンデンサをサーボの電源と並列につなぎます。
- まずキャリブレーションのコード、次に追尾のコードを書き込み、懐中電灯でシステムをテストします。
ステップ1:LDRのテストとキャリブレーション
同じ袋に入っていたLDRでも、同じ明るさで値がはっきり違います。GL5528のデータシートでは、10ルクスで10〜20 kΩという広い範囲が示されています。キャリブレーションしていないセンサーは、太陽にまっすぐ向いていても片側が明るいと判断し、パネルを数度間違った方向へ回してしまいます。下のコードを書き込み、「ツール」メニューからシリアルプロッタを開いて、センサーのブロックを均一に照らしてください。たとえば、ランプの前に白い紙を張ると、光が拡散してむらがなくなります。
1// 4 LDR'yi okuyup Seri Çizici'ye yazar / reads 4 LDRs for the Serial Plotter
2const int LDR[4] = {A0, A1, A2, A3}; // sol üst, sağ üst, sol alt, sağ alt / TL, TR, BL, BR
3
4void setup() {
5 Serial.begin(9600);
6}
7
8void loop() {
9 for (int i = 0; i < 4; i++) {
10 Serial.print(analogRead(LDR[i]));
11 Serial.print(i < 3 ? '\t' : '\n'); // sekmeyle ayır / separate with tabs
12 }
13 delay(100);
14}同じ明るさなら、4本の線は同じ高さに並ぶはずです。値が612、640、598、625だったとしましょう。平均は619です。各LDRの補正値は、平均から自分の値を引いたもので、+7、−21、+21、−6になります。この数字をステップ3のコードのOFFSET配列に入力します。LDRの抵抗は光に対して直線的には変わらないので、システムが実際に動く明るさに近い条件でキャリブレーションすると、最も正確な結果になります。シリアルプロッタではノイズも見えます。何にも触れていないときに値が何カウント揺れるかメモしておいてください。不感帯を決めるときにその数字が必要になります。
ステップ2:1軸の太陽追尾装置のコード
まず1軸で考え方を見てみましょう。縦の影の壁で仕切った2つのLDRが東側と西側を向いています。A0が東のLDR、A1が西のLDRです。コードは片方の値からもう片方を引き、差が不感帯より大きければサーボを明るい側へ1°回し、そうでなければそのままにします。この短いスケッチだけで、完全な1軸のシステムになります。パネルが光から逃げる場合は、2つの条件のangle++とangle--を入れ替えてください。
1#include <Servo.h>
2
3Servo servo;
4const int LDR_EAST = A0; // doğu tarafındaki LDR / east-side LDR
5const int LDR_WEST = A1; // batı tarafındaki LDR / west-side LDR
6const int TOL = 30; // ölü bölge (ADC birimi) / dead band (ADC counts)
7int angle = 90; // başlangıç: tam güney / start: due south
8
9void setup() {
10 servo.attach(9);
11 servo.write(angle);
12}
13
14void loop() {
15 int diff = analogRead(LDR_EAST) - analogRead(LDR_WEST);
16 if (diff > TOL && angle < 180) angle++; // doğu daha aydınlık / east is brighter
17 if (diff < -TOL && angle > 0) angle--; // batı daha aydınlık / west is brighter
18 servo.write(angle);
19 delay(30); // servo yetişsin / let the servo settle
20}delay(30)なら、サーボは30 msごとに最大1°、つまり1秒に約33°回るので、デモではパネルが懐中電灯を楽に追いかけます。太陽の動きはずっと遅いので、実際の使用では数秒ごとの補正で十分です。
ステップ3:2軸のコード、不感帯、データの記録
完全版は、4つのLDRから2つの差を計算し、軸ごとに別々に補正し、夜になるとパネルを朝に備えた位置に戻し、1分に1回、両方のパネルの電圧、電力、集めたエネルギーをCSVとしてシリアルモニタに書き出します。コードはdelay()ではなくmillis()を使うので、追尾と記録が互いを待たずに動きます。この手法の詳細はmillisとdelayの違いにあります。
1#include <Servo.h>
2
3// Pinler / Pins
4const int LDR[4] = {A0, A1, A2, A3}; // sol üst, sağ üst, sol alt, sağ alt / TL, TR, BL, BR
5const int PANEL_TRACK = A4; // takip eden panelin bölücüsü / tracking panel divider
6const int PANEL_FIXED = A5; // sabit panelin bölücüsü / fixed panel divider
7
8// Ayarlar / Settings
9const int OFFSET[4] = {0, 0, 0, 0}; // Adım 1'deki düzeltmeler / corrections from step 1
10const int TOL_START = 40; // fark bunu aşınca dön / start turning above this
11const int TOL_STOP = 15; // fark bunun altına inince dur / stop below this
12const int DIR_H = 1, DIR_V = 1; // ters dönüyorsa -1 yap / set to -1 if it turns away
13const int H_MIN = 0, H_MAX = 180; // yatay sınırlar / horizontal limits
14const int V_MIN = 10, V_MAX = 90; // dikey sınırlar / vertical limits
15const int H_PARK = 180, V_PARK = 45; // gece konumu: doğu (batıda bekliyorsa H_PARK = 0) / night position: east (0 if it waits facing west)
16const int NIGHT = 60; // ortalama bunun altındaysa gece / below this average = night
17const float DIVIDER = 3.2; // (22k + 10k) / 10k
18const float LOAD_OHM = 33.0; // yük direnci / load resistor
19const unsigned long LOG_MS = 60000; // kayıt aralığı: 1 dk / log interval: 1 min
20
21Servo servoH, servoV; // yatay ve dikey eksen / horizontal and vertical axis
22int angleH = 90, angleV = 45;
23bool movingH = false, movingV = false;
24unsigned long lastStep = 0, lastLog = 0, darkSince = 0;
25float mWhTrack = 0, mWhFixed = 0; // toplanan enerji / collected energy
26
27int readAvg(int pin) { // 8 okumanın ortalaması / average of 8 readings
28 long sum = 0;
29 for (int i = 0; i < 8; i++) sum += analogRead(pin);
30 return sum / 8;
31}
32
33// Ölü bölge + histerezis / dead band + hysteresis
34int correct(int diff, bool &moving, int angle, int dir, int lo, int hi) {
35 if (abs(diff) > TOL_START) moving = true;
36 if (abs(diff) < TOL_STOP) moving = false;
37 if (moving) angle += (diff > 0 ? 1 : -1) * dir;
38 return constrain(angle, lo, hi);
39}
40
41float panelVolts(int pin) { // bölücüden panel gerilimi / panel voltage from divider
42 return readAvg(pin) * 5.0 / 1023.0 * DIVIDER;
43}
44
45void setup() {
46 Serial.begin(9600);
47 servoH.attach(9);
48 servoV.attach(10);
49 servoH.write(angleH);
50 servoV.write(angleV);
51 Serial.println("dk,V_takip,V_sabit,mW_takip,mW_sabit,mWh_takip,mWh_sabit");
52}
53
54void loop() {
55 unsigned long now = millis();
56
57 if (now - lastStep >= 40) { // saniyede 25 düzeltme / 25 corrections per second
58 lastStep = now;
59 int v[4];
60 for (int i = 0; i < 4; i++) v[i] = readAvg(LDR[i]) + OFFSET[i];
61 int avg = (v[0] + v[1] + v[2] + v[3]) / 4;
62 int dV = (v[0] + v[1]) / 2 - (v[2] + v[3]) / 2; // üst - alt / top - bottom
63 int dH = (v[0] + v[2]) / 2 - (v[1] + v[3]) / 2; // sol - sağ / left - right
64
65 if (avg > NIGHT) {
66 darkSince = now;
67 angleV = correct(dV, movingV, angleV, DIR_V, V_MIN, V_MAX);
68 angleH = correct(dH, movingH, angleH, DIR_H, H_MIN, H_MAX);
69 } else if (now - darkSince > 600000UL) { // 10 dk karanlık / 10 min of darkness
70 angleH = H_PARK;
71 angleV = V_PARK;
72 }
73 servoH.write(angleH);
74 servoV.write(angleV);
75 }
76
77 if (now - lastLog >= LOG_MS) { // dakikada bir CSV satırı / one CSV line per minute
78 lastLog = now;
79 float vt = panelVolts(PANEL_TRACK), vf = panelVolts(PANEL_FIXED);
80 float pt = vt * vt / LOAD_OHM * 1000.0; // P = V² / R (mW)
81 float pf = vf * vf / LOAD_OHM * 1000.0;
82 mWhTrack += pt * LOG_MS / 3600000.0; // enerji = güç × süre / energy = power × time
83 mWhFixed += pf * LOG_MS / 3600000.0;
84 Serial.print(now / 60000); Serial.print(',');
85 Serial.print(vt, 2); Serial.print(',');
86 Serial.print(vf, 2); Serial.print(',');
87 Serial.print(pt, 1); Serial.print(',');
88 Serial.print(pf, 1); Serial.print(',');
89 Serial.print(mWhTrack, 1); Serial.print(',');
90 Serial.println(mWhFixed, 1);
91 }
92}| 設定 | 値 | 役割 |
|---|---|---|
| OFFSET | ステップ1の補正値 | LDRの製造上のばらつきを打ち消す |
| TOL_START / TOL_STOP | 40 / 15 | ヒステリシス付きの不感帯。差が40を超えると回り、15を下回ると止まる |
| DIR_H / DIR_V | 1または-1 | 取り付けに合わせて回転の向きを反転する |
| V_MIN / V_MAX | 10 / 90 | 縦のサーボの範囲。パネルがブラケットにぶつからないようにする |
| NIGHT | 60 | 4つのLDRの平均がこれを下回ると夜とみなす |
| H_PARK / V_PARK | 180 / 45 | 暗くなって10分後にパネルが待機する東向きの位置。西を向いて待つ場合はH_PARKを0にする |
| DIVIDER / LOAD_OHM | 3.2 / 33 | パネルの電圧と、P = V² / Rでの電力を計算するため |
| LOG_MS | 60000 | 記録の間隔:1分に1行 |
不感帯(許容範囲)で震えを防ぐ
不感帯がないと、システムは決して休みません。太陽にまっすぐ向いていても、ノイズのせいでLDRの値が数カウント揺れ、差がプラスとマイナスの間で入れ替わり、サーボが右に1°、左に1°と動き続けます。この震えはサーボのギアをすり減らし、エネルギーも無駄にします。解決法は、小さな差を無視することです。差が一定の限度より小さければ何もしません。限度はキャリブレーションで見たノイズをもとに決め、不感帯をノイズの少なくとも3〜4倍にします。Unoでノイズが約±5カウントなら、20〜40がよい出発点です。
コードの方法はさらに一歩進んだヒステリシスです。サーボは差が40を超えると動き始めますが、15を下回るまで止まりません。限度が1つだけの不感帯だと、パネルは限度のぎりぎりで止まり、小さな雲や突風でまた動き出します。2つの限度を持つシステムなら、毎回パネルを太陽にしっかり合わせてから落ち着きます。不感帯がエネルギーに与える影響も無視できるほどです。コサインの表で見たとおり、数度の誤差は1%未満の損失にしかなりません。ほかのフィルタリングの手法はセンサーデータのフィルタリングを参照してください。
効率の実験:追尾パネルと固定パネル
追尾装置が効果があることを証明する唯一の方法は、測ることです。ここでよくある間違いは、パネルの開放電圧だけを測ることです。パネルの無負荷の電圧は光によってほとんど変わりません。光が半分になると電流もほぼ半分になりますが、開放電圧は3〜5%しか下がりません。そのため両方のパネルが約7 Vを示し、差が消えてしまいます。正しい方法は、各パネルに同じ負荷抵抗をつなぎ、その両端の電圧を測ることです。電力はP = V² / Rで求め、エネルギーは電力を時間で足し合わせて求めます。負荷抵抗はパネルの最大電力点に合わせて選びます。6 V 1 Wのパネルは約1 / 6 = 0.17 Aを出すので、合う抵抗は6 / 0.17 ≈ 36 Ωで、標準値の33 Ωがよい選択です。抵抗は1 W近くを熱として消費するので、少なくとも2 W、できれば5 Wのセメント抵抗を使ってください。数値はオームの法則計算ツールで確認できます。
- 同じパネルを2枚使います。同じモデルで、できれば同じロットのものです。両方に33 Ωの負荷抵抗と22 kΩ / 10 kΩの分圧回路をつなぎ、追尾パネルをA4に、固定パネルをA5につなぎます。
- 予備テストをします。両方のパネルを並べて同じ向きと傾きで取り付け、30〜60分測ります。差(たとえば2%)をメモし、その値で結果を補正します。さらによいのは、2日目にパネルを入れ替えることです。
- 固定パネルは真南に向け、傾きはその日の正午の太陽に合わせます(下の注意を参照)。
- 2枚のパネルは、互いやセンサーに影を落とさない距離をとり、木や壁の影がない場所に置きます。
- パネルの電圧を一度テスターで測り、Arduinoの値と比べます。違っていればDIVIDERの値を補正します(テスターの使い方)。
- 日の出から日の入りまで1分に1回記録し、天気(快晴、晴れ時々曇り、曇り)を書き留めます。
- 少なくとも3日の晴れた日に実験を繰り返し、曇りの日を1日別に記録して、1日の平均エネルギーを比べます。
| 列 | 意味 |
|---|---|
| dk | Arduinoが起動してからの分数 |
| V_takip / V_sabit | 追尾 / 固定パネルの負荷抵抗の両端の電圧(V) |
| mW_takip / mW_sabit | 瞬間の電力:V² / R × 1000 |
| mWh_takip / mWh_sabit | 記録を始めてから集めたエネルギー |
| 増加(%) | (mWh_takip − mWh_sabit) / mWh_sabit × 100。表計算ソフトで計算する |
シリアルモニタのデータはコピーしてCSVファイルとして保存するか、直接Excelに流し込めます(ArduinoのデータをExcelに送る)。パソコンのそばにいたくなければ、SDカードモジュールを追加してください。晴れた日のグラフでは、追尾パネルの線が朝と夕方にはっきり上にあり、正午ごろには2本の線がほぼ重なります。大規模なシステムでは、1軸の追尾で約15〜35%、2軸で30〜45%のエネルギーの増加が報告されています。ミニの装置では、結果は天気、季節、固定パネルの向きの良し悪しに大きく左右されます。レポートでは、測ったのがパネル自体の効率ではなく、同じパネルが集めたエネルギーの増加であることをはっきり書いてください。制約も1つ加えましょう。負荷抵抗が固定だと電力は電流の2乗で変わるので、光が弱まるとパネルの電力は光より速く下がります。そのため、測った増加の割合は、充電コントローラーのある実際のシステムより高く出ることがあります。2枚のパネルの電流(V / R)も合計して比べれば、届く光の差が実際にどれくらいかもわかります。
エネルギーの収支:システムは自分の電力をまかなえる?
これは審査員が好んでする質問です。追尾装置はエネルギーを増やしますが、自分でも消費します。ミニの模型のおおよその数値は次のとおりです。
| 項目 | おおよその値 | 1日のエネルギー |
|---|---|---|
| Arduino Uno(常時オン) | 45〜50 mA、5 Vで約0.25 W | 約6 Wh(24時間) |
| SG90 × 2(停止中) | サーボ1個あたり数mA〜10 mA | 約1〜2.5 Wh(24時間) |
| SG90(動作中) | 100〜250 mA、短時間 | 補正の頻度による。通常は小さい |
| ESP32(Wi-Fiオン) | 平均80〜150 mA | 約10〜18 Wh(24時間) |
| 6 V 1 Wのミニパネル(晴れた日) | 最大1 W | 約3〜5 Whを発電 |
表からはっきりわかるとおり、ミニの模型は自分の電子回路を動かすのに十分なエネルギーを作れません。追尾で増える約1〜1.5 Whは、Unoが1日に使う約6 Whに比べて小さな値です。これは失敗ではなく、正直で価値のある結果です。規模が大きくなると状況は逆転します。キロワット級のパネルを回すモーターとコントローラーの消費は、発電量のごく一部になります。ミニのシステムで消費を減らすには、1分に1回だけ補正し、動いていないサーボはdetach()で解放し、Unoの代わりにスリープするPro Miniや、ディープスリープを使うESP32を選んでください。電池の持ちを見積もるには、電池の持続時間の計算ツールとmAhとWhのガイドを使ってください。
ESP32で太陽を追尾し、スマホで監視する

ESP32は、より細かいADCとWi-Fiで同じロジックを動かします。注意すべき違いがいくつかあります。アナログの値は0〜4095なので、不感帯はUnoの約4倍にします。Wi-Fiがオンの間は、ADC1のピン(GPIO 32〜39)しか確実に動きません。ADC2のピンはWi-Fiのドライバが使っているからです。サーボ用にライブラリマネージャからESP32Servoライブラリをインストールしてください。パネルの電圧を測るのに使うanalogReadMilliVolts()関数は、工場でのキャリブレーションでESP32のADCの非直線的な振る舞いを補正します。LDRの分圧回路は必ず3V3から給電してください。書き込んだら、シリアルモニタに表示されたIPアドレスを同じネットワークにつないだスマホで開くと、両方のパネルのリアルタイムの電圧、集めたエネルギー、増加が表示されます。ESP32が初めてならESP32とは?から始めてください。
1#include <WiFi.h>
2#include <WebServer.h>
3#include <ESP32Servo.h>
4
5const char* ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
6const char* password = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
7
8const int LDR[4] = {32, 33, 34, 35}; // sol üst, sağ üst, sol alt, sağ alt (ADC1) / TL, TR, BL, BR (ADC1)
9const int PANEL_TRACK = 36; // VP: takip eden panel / tracking panel
10const int PANEL_FIXED = 39; // VN: sabit panel / fixed panel
11const int TOL_START = 160, TOL_STOP = 60; // 12 bit ölçek: Uno'nun 4 katı / 12-bit scale: 4x the Uno
12const int DIR_H = 1, DIR_V = 1; // ters dönüyorsa -1 yap / set to -1 if it turns away
13const int NIGHT = 240; // gece eşiği / night threshold
14const float DIVIDER = 3.2, LOAD_OHM = 33.0;
15
16Servo servoH, servoV;
17WebServer server(80);
18int angleH = 90, angleV = 45;
19bool movingH = false, movingV = false;
20unsigned long lastStep = 0, lastLog = 0;
21float vt = 0, vf = 0, mWhTrack = 0, mWhFixed = 0;
22
23int readAvg(int pin) {
24 long sum = 0;
25 for (int i = 0; i < 8; i++) sum += analogRead(pin);
26 return sum / 8;
27}
28
29float panelVolts(int pin) { // kalibre edilmiş mV okuması / calibrated mV reading
30 long mv = 0;
31 for (int i = 0; i < 8; i++) mv += analogReadMilliVolts(pin);
32 return mv / 8 / 1000.0 * DIVIDER;
33}
34
35int correct(int diff, bool &moving, int angle, int dir, int lo, int hi) {
36 if (abs(diff) > TOL_START) moving = true;
37 if (abs(diff) < TOL_STOP) moving = false;
38 if (moving) angle += (diff > 0 ? 1 : -1) * dir;
39 return constrain(angle, lo, hi);
40}
41
42void handleRoot() { // telefondan izleme sayfası / page for your phone
43 float gain = mWhFixed > 0 ? (mWhTrack - mWhFixed) / mWhFixed * 100.0 : 0;
44 String s = "Takip / tracking: " + String(vt, 2) + " V, " + String(mWhTrack, 1) + " mWh\n";
45 s += "Sabit / fixed: " + String(vf, 2) + " V, " + String(mWhFixed, 1) + " mWh\n";
46 s += "Kazanc / gain: %" + String(gain, 1) + "\n";
47 s += "Aci / angle: H " + String(angleH) + ", V " + String(angleV) + "\n";
48 server.send(200, "text/plain", s);
49}
50
51void setup() {
52 Serial.begin(115200);
53 servoH.setPeriodHertz(50); // standart servo: 50 Hz / standard servo: 50 Hz
54 servoV.setPeriodHertz(50);
55 servoH.attach(18, 500, 2400); // 500–2400 µs darbe / pulse
56 servoV.attach(19, 500, 2400);
57 servoH.write(angleH);
58 servoV.write(angleV);
59 WiFi.begin(ssid, password);
60 for (int i = 0; i < 40 && WiFi.status() != WL_CONNECTED; i++) delay(500); // en fazla 20 s bekle; Wi-Fi yoksa da takip eder / wait up to 20 s; tracks even without Wi-Fi
61 Serial.println(WiFi.localIP()); // bu adresi telefonda aç / open this address on your phone
62 server.on("/", handleRoot);
63 server.begin();
64}
65
66void loop() {
67 server.handleClient();
68 unsigned long now = millis();
69
70 if (now - lastStep >= 40) {
71 lastStep = now;
72 int v[4];
73 for (int i = 0; i < 4; i++) v[i] = readAvg(LDR[i]);
74 int avg = (v[0] + v[1] + v[2] + v[3]) / 4;
75 int dV = (v[0] + v[1]) / 2 - (v[2] + v[3]) / 2; // üst - alt / top - bottom
76 int dH = (v[0] + v[2]) / 2 - (v[1] + v[3]) / 2; // sol - sağ / left - right
77 if (avg > NIGHT) { // gece hareket etme / do not move at night
78 angleV = correct(dV, movingV, angleV, DIR_V, 10, 90);
79 angleH = correct(dH, movingH, angleH, DIR_H, 0, 180);
80 servoV.write(angleV);
81 servoH.write(angleH);
82 }
83 }
84
85 if (now - lastLog >= 60000) { // dakikada bir ölç / measure once a minute
86 lastLog = now;
87 vt = panelVolts(PANEL_TRACK);
88 vf = panelVolts(PANEL_FIXED);
89 mWhTrack += vt * vt / LOAD_OHM * 1000.0 / 60.0; // 1 dakikalık enerji (mWh) / energy of one minute
90 mWhFixed += vf * vf / LOAD_OHM * 1000.0 / 60.0;
91 Serial.printf("%.2f,%.2f,%.1f,%.1f\n", vt, vf, mWhTrack, mWhFixed);
92 }
93}測定値を何日も残したい場合は、同じ値をオンラインの表計算シートに送れます。ESP32のデータをGoogleスプレッドシートに記録するを参照してください。ADCの分解能と基準電圧についてはADCとアナログ・デジタル変換で詳しく解説しています。
Raspberry Pi PicoとMicroPythonで1軸の追尾

Picoが外に出しているアナログ入力は3本(GP26、GP27、GP28)だけで、4つのLDRと2枚のパネルには足りません。そのため、Picoで最もすっきりした解決法は1軸の追尾装置です。2つのLDRで方向を見つけ、3本目のアナログ入力でパネルの電圧を測ります。2軸にするには、4チャンネルのADS1115のような外付けのI2C ADCを追加できます。サーボは50 HzのPWMで直接駆動します。500 µsのパルスが0°、2400 µsのパルスが180°です。read_u16()は0〜65535のスケールで値を返すので、不感帯は2000のような大きな数になります。PicoへのMicroPythonのインストール方法はRaspberry Pi Picoとは?にあります。
1from machine import Pin, PWM, ADC
2import time
3
4east = ADC(26) # GP26: doğu LDR'si / east LDR
5west = ADC(27) # GP27: batı LDR'si / west LDR
6panel = ADC(28) # GP28: panel bölücüsü / panel divider
7
8servo = PWM(Pin(15)) # GP15: servo sinyali / servo signal
9servo.freq(50) # 50 Hz = 20 ms periyot / period
10
11TOL = 2000 # ölü bölge (0–65535 ölçeği) / dead band (0–65535 scale)
12DIVIDER = 3.2 # (22k + 10k) / 10k
13LOAD = 33.0 # yük direnci (ohm) / load resistor (ohm)
14
15def set_angle(a):
16 # 0° = 500 µs, 180° = 2400 µs darbe / pulse
17 servo.duty_ns(int((500 + a * 1900 / 180) * 1000))
18
19def read(adc, n=8):
20 return sum(adc.read_u16() for _ in range(n)) // n
21
22angle = 90
23set_angle(angle)
24energy = 0.0 # mWh
25last_log = time.ticks_ms()
26
27while True:
28 diff = read(east) - read(west)
29 if diff > TOL and angle < 180:
30 angle += 1 # doğuya dön / turn east
31 elif diff < -TOL and angle > 0:
32 angle -= 1 # batıya dön / turn west
33 set_angle(angle)
34
35 if time.ticks_diff(time.ticks_ms(), last_log) >= 60000:
36 last_log = time.ticks_ms()
37 v = read(panel) * 3.3 / 65535 * DIVIDER
38 p = v * v / LOAD * 1000 # mW
39 energy += p / 60 # 1 dakikalık enerji / energy of one minute
40 with open("log.csv", "a") as f: # Pico'nun kendi belleğine yaz / write to Pico's flash
41 f.write("{},{:.2f},{:.1f},{:.1f}\n".format(angle, v, p, energy))
42 time.sleep_ms(30)コードは測定値をPico自身のフラッシュメモリにあるlog.csvファイルに追記します。1分に1行で1日30〜40 KBになるので、Picoの約1.4 MBのファイルシステムなら何週間も持ちます。ファイルをmain.pyとして保存すれば、パソコンをつながなくても、電池やUSBアダプターで給電した途端にPicoがプログラムを自動で始めます。1日の終わりに、Thonnyでlog.csvをパソコンにダウンロードし、Excelで開けます。
科学コンテストで発表するためのヒント
太陽追尾装置は科学コンテストや学校の競技会でよくあるプロジェクトです。自分の作品を際立たせるのは模型そのものではなく、測定したデータとその解釈です。プロジェクトのアイデアとレポートの構成については、Arduinoの科学コンテスト向けプロジェクトアイデアも見てください。
- 測定できる問いを立てる:晴れた秋の日、2軸の追尾は、南向きの最適な角度に固定した同じパネルより何パーセント多くのエネルギーを集めるか?
- 先に仮説を書く:たとえば、増加は朝と夕方に集中すると予想します。結果がそれを裏付けなくても、その理由を考察すれば評価が上がります。
- 変数を分ける:独立変数は追尾の方法(なし、1軸、2軸)、従属変数は1日のエネルギー(Wh)、制御変数はパネルのモデル、負荷抵抗、場所、日付です。
- グラフで伝える:時間と電力の折れ線グラフで両方のパネルを重ねて示し、1日のエネルギーを棒グラフで比べ、日ごとのばらつきを誤差の範囲として示します。
- 正味のエネルギーを考察する:システム自体の消費を測り、エネルギーの収支の表をポスターに載せます。プロジェクトが誠実で科学的であることが伝わります。
- 展示会場には太陽がない:ライブデモは懐中電灯かスポットライトで行い、屋外の実験のタイムラプス動画と生のCSVファイルを持参します。
- ポスターを構成する:問題、方法、回路図、コードのフローチャート、結果、制約、提案。どのセクションも短い文と大きなグラフで。
- 制約を自分から述べる:小さなパネル、数日分しかないデータ、風、LDRのばらつきといった制約を審査員に聞かれる前に述べると、信頼が生まれます。
| 審査員から聞かれそうな質問 | 用意しておく答え |
|---|---|
| なぜ時計方式ではなくLDRを使ったのですか? | 時計方式は雲の下でも動くが場所と時刻の設定が必要で、LDRでの追尾は準備が簡単で自分の誤差を補正できるが雲の下では不安定であることを説明する。 |
| システムは自分が使うエネルギーをまかなえますか? | エネルギーの収支の表で答える。ミニのシステムではまかなえないが、規模が大きくなると消費は発電量のごく一部になる。 |
| 固定パネルはどう向けましたか? | 真南に、その日の正午の太陽に最適な傾きで向けた。予備テストで2枚のパネルの差も測ったことを示す。 |
| 曇りの日はどうなりますか? | 光が拡散しているので、増加はほぼゼロになる。不感帯がパネルの無駄な動きを防ぐ。曇りの日のデータを示す。 |
| 追尾装置は実際の発電所で使われていますか? | はい。大規模な太陽光発電所では1軸の追尾が一般的。2軸のシステムは費用がかかり、保守も多く必要。 |
| 測定誤差をどう減らしましたか? | 8回の読み取りの平均、テスターでの校正、パネルの入れ替え、数日にわたる実験の繰り返しを説明する。 |
よくある間違いと解決法
| 問題 | 考えられる原因 | 解決法 |
|---|---|---|
| パネルが光から逃げるように回る | 取り付けに対してサーボの向きが逆 | DIR_HかDIR_Vを-1にする |
| サーボが左右に震える | 不感帯が狭すぎる、値にノイズが多い | TOL_STARTとTOL_STOPを大きくし、平均する回数を増やす |
| パネルが太陽から少しずれた位置で止まる | LDRをキャリブレーションしていない、またはセンサーがパネルと平行でない | キャリブレーションし、センサーのブロックをパネルの表面にそろえる |
| サーボが動くとArduinoがリセットされる | サーボをボードの5Vピンから給電している | 外部の5 V電源、共通GND、470 µFのコンデンサを使う |
| 日なたでは4つの値がすべて1000近くになり、差がない | 固定抵抗が大きすぎて、値が飽和している | 屋外では1〜2.2 kΩを使い、影の壁を高くする |
| 両方のパネルが7 Vを示し、差が見えない | 負荷抵抗がなく、開放電圧を測っている | 各パネルに33 Ωの負荷抵抗をつなぐ |
| ESP32でWi-Fiが始まるとアナログ値がおかしくなる | LDRがADC2のピンにつながっている | GPIO 32〜39のADC1のピンを使う |
| 夕方にパネルが西を向いたままで、朝の回転が遅れる | 夜の待機位置が取り付けと合っていない、またはNIGHTが低すぎる | H_PARKを取り付けの東側の端(180か0)にする。夜の平均がNIGHTより高いなら、しきい値を上げる |
| 曇りの日にパネルがでたらめに動き回る | 拡散光では差が小さく不安定 | 不感帯を広げ、NIGHTのしきい値を上げて暗いときは動かないようにする |
プロジェクトをさらに発展させるアイデア
- 太陽光の充電ステーション:追尾パネルをTP4056充電モジュールと18650セルにつなぎ、固定パネルと充電時間を比べます。
- ハイブリッドの追尾:RTC時計モジュールで太陽の位置を計算し、LDRは微調整だけに使います。曇りの日でもパネルが正しい方向を向きます。
- Wi-Fiでのデータ記録:ESP32から測定値をGoogleスプレッドシートに送り、スマホでグラフをリアルタイムに見ます。
- 太陽光で動く温室:追尾パネルでスマート温室の模型のファンと水やりに給電します。
- 光のスキャナー:1つのLDRをサーボで0〜180°振り、最も明るい方向を見つける版を作ります。考え方はArduinoのレーダープロジェクトによく似ています。
- より正確な電力の測定:負荷抵抗の横に、電流と電圧を同時に測るINA219のようなI2Cの電力センサーを使い、パネルの電力曲線を描きます。
太陽追尾装置は、センサーの読み取り、判断、モーター制御、科学的な測定を1つのプロジェクトにまとめているので、学ぶにも競技会で見せるにも最適な選択です。まず1軸から始め、不感帯を調整し、次に2つ目の軸と固定パネルとの比較実験を加えましょう。BlueGraysアプリには、LDRでの明るさの測定、ナイトライト、サーボの角度制御、デジタル電圧計など、このシステムの各部分を1つずつ試せるプロジェクトが、回路図とコード付きで揃っています。
よくある質問
太陽追尾装置で発電量はどれくらい増える?
大規模なシステムでは、1軸の追尾で固定パネルより約15〜35%、2軸の追尾で約30〜45%多くのエネルギーを集めます。増加が最も大きいのは晴れた日と、太陽が低い朝と夕方で、正午ごろはほぼゼロです。自分の装置での実際の値を知るには、同じ日に同じ固定パネルと比べて測ってください。
太陽追尾装置にはLDRがいくつ必要?
1軸の追尾装置なら2つ、2軸なら4つのLDRで十分です。間に影の壁を置きます。太陽が片側に動くと、壁が反対側のLDRに影を落とし、Arduinoはその差をもとにパネルを回します。LDRを増やしても、精度は目に見えては上がりません。
太陽追尾装置は1軸と2軸のどちらを作るべき?
最初のプロジェクトなら1軸から始めてください。サーボ1つとLDR2つで、シンプルな機構のまま増加分の大半が得られます。2軸は少し多くのエネルギーを集め、科学コンテストでも見栄えがしますが、パンチルトブラケットと2軸の同時の調整が必要です。
太陽追尾装置にはどのサーボを使えばいい?
6 V 1 Wのモデルのような軽いミニパネルならSG90で十分です。風のある屋外では、金属ギアのMG90Sのほうが丈夫です。5 W以上の大きなパネルには、MG996Rのような高トルクのサーボかギア付きモーターが必要です。サーボは必ず外部の5 V電源から給電し、グランドを共通にしてください。
太陽追尾装置のサーボが震えるのはなぜ?
LDRの値はノイズのせいで常に数カウント揺れています。コードに不感帯(許容範囲)がないと、サーボはわずかな差のたびに行ったり来たりします。限度未満の差を無視する不感帯、さらに始動と停止に別々の限度を設ける方法(ヒステリシス)で震えは止まります。サーボの電源が弱いことが原因の場合もあります。
太陽追尾装置は自分の電力をまかなえる?
ミニの模型では無理です。Arduino Unoは1日に約6 Whを使いますが、6 V 1 Wのパネルが晴れた日に作るのは3〜5 Whです。ただし大規模なシステムでは、モーターとコントローラーが使うのは発電量のごく一部です。この計算をレポートで示すと、プロジェクトの科学的な説得力が増します。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
LDRで光の強さを測定
部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

LDRで作る常夜灯
暗くなると自動で点灯し、朝になると消灯する小さな常夜灯です。光センサーが部屋の明るさを測定し、Arduinoが十分に暗くなったらLEDを点灯させ、明るくなったら消灯させます。街灯が夕方になると自動で点灯する仕組みも理解できます。

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

サーボモーターの使い方
小型サーボモーターのアームが、一方の端、中央、もう一方の端の3つの位置へ順番に回り、それぞれの位置で2秒間止まります。Arduinoが保持する角度をモーターに指示すると、モーターはその位置まで動いて止まります。ロボットアームや自動開閉するふたが動く仕組みを学べます。

ESP32サーボモーター(ポテンショメーター制御)
このプロジェクトでは、ポテンショメーターでSG90サーボモーターを制御します。ポテンショメーターから読み取ったアナログ値を0~180度の範囲に変換し、サーボの角度を調整します。ESP32Servoライブラリは、ESP32の出力に対応した適切なGPIOでサーボ信号を生成します。このプロジェクトではGPIO 18を使います。ロボットアームやカメラ用ジンバル、自動ドアの機構に使われる基本的な制御方法です。

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

ESP32とLDRで作るナイトライト
暗くなるとLEDが自動で点灯し、明るくなると消灯するナイトライトです。LDRセンサーモジュールのD0(デジタル出力)ピンを使います。モジュール上の小さな可変抵抗で暗さのしきい値を設定すると、モジュール自体がHIGH/LOWを出力します。ESP32はdigitalRead()でこの信号を読み取るだけなので、コードはとてもシンプルです。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

