高精度サーボ駆動ソーラートラッカー - 最大発電効率を実現する自動追尾プラットフォーム
必要な部品と用品
| フォトレジスタ(CdSセル) | ×4 | |
| Adafruit Flora RGB NeoPixel LED(4個パック) | ×1 | 同等のフルカラーLEDで代替可 |
| タクトスイッチ 12mm | ×2 | 同等のノーマルオープンプッシュボタンで代替可 |
| 温度センサー | ×1 | |
| Arduino UNO | ×1 | |
| 180°サーボモーター | ×2 | Futaba S3003等の同等品で可 |
| 抵抗 10kΩ | ×6 | |
| 抵抗 220Ω | ×1 | |
| コンデンサ 100µF | ×2 | |
| ソーラーパネル(80mm × 80mm) | ×1 | Amazon等で入手可 |
必要な工具と材料
- 手工具一式、ブラケット用の小型ナット・ボルト
- 「ツイスト(水平回転)」用サーボモーターの固定ベース(木ブロック等)
- 「L字ブラケット」用の曲げ加工可能な金属板またはアルミ板(約3mm厚、幅2cm弱)
- ソーラーパネル台座とツイストサーボ接続用の薄い金属板(HVACダクト固定用の薄いスチールストリップ等、穴あきで加工しやすい)
使用するアプリとオンラインサービス
- Arduino IDE
- Microsoft Excel(ドキュメント・メモ用)
プロジェクト概要
2つの180°サーボと4つのフォトレジスタを用いて、小型ソーラーパネルを最も光量の多い方向へ自動追尾させる実装例です。
モーターは小刻みに動き、パネルを最も明るい方向へ向けようとします。システム有効/無効を示すフルカラーLEDと、モーター有効/無効用の2つのプッシュボタンを備えています。
シリアルポートインターフェース経由で数値コマンドを送信し、モーターを指定位置へ動かすルーチンも用意。機械構造への衝撃を避けるため、即座に目標位置へジャンプするのではなく、滑らかな動きで移動するようプログラムされています。
コード
Q2コード (Arduino) - Version 2.0
// Scott Mangiacotti
// Tucson, Arizona USA
// May 2018
// Q2 Version 2.0
#include <Servo.h>
// 定数
const int GIVE_BACK_TIME = 125; // ループ間隔(ms)
// 入力ピン
const int I_RED_BUTTON_PIN = 4; // 赤ボタン(停止)
const int I_GREEN_BUTTON_PIN = 2; // 緑ボタン(開始)
const int I_LDR1_PIN = A3; // 左上
const int I_LDR2_PIN = A2; // 右上
const int I_LDR3_PIN = A1; // 左下
const int I_LDR4_PIN = A0; // 右下
// 出力ピン
const int O_RED_LED_PIN = 9; // RGB LED 赤
const int O_GREEN_LED_PIN = 11; // RGB LED 緑
const int O_BLUE_LED_PIN = 10; // RGB LED 青
const int O_TWIST_SERVO_PIN = 5; // 水平回転サーボ
const int O_TILT_SERVO_PIN = 6; // 垂直傾斜サーボ
// グローバル変数
bool gRunning = false; // システム稼働中フラグ
bool gTrackToLightEnabled = false; // 光追尾モードフラグ
bool gVerboseDiagMode = false; // 診断モードフラグ
Servo gServoTwist;
int gServoTwistPositionCommand; // 目標位置
bool gServoTwistMoveIP; // 移動中フラグ
Servo gServoTilt;
bool gServoTiltMoveIP;
int gServoTiltPositionCommand;
int gLDR1, gLDR2, gLDR3, gLDR4;
void setup() {
Serial.begin(9600);
pinMode(I_RED_BUTTON_PIN, INPUT);
pinMode(I_GREEN_BUTTON_PIN, INPUT);
pinMode(O_RED_LED_PIN, OUTPUT);
pinMode(O_GREEN_LED_PIN, OUTPUT);
pinMode(O_BLUE_LED_PIN, OUTPUT);
startup(); // 起動シーケンス実行
}
void loop() {
// シリアルコマンド処理
if (Serial.available() > 0) {
int iControlCode = Serial.parseInt();
processSerialMessage(iControlCode);
}
// 緑ボタン:追尾開始
int iGreenButton = digitalRead(I_GREEN_BUTTON_PIN);
if (iGreenButton == HIGH && gRunning == false) {
enableTracking();
gTrackToLightEnabled = true;
}
// 赤ボタン:追尾停止
int iRedButton = digitalRead(I_RED_BUTTON_PIN);
if (iRedButton == HIGH && gRunning == true) {
disableTracking();
}
// センサー値読み取り
readPhotoResistors();
averageTopTwoSensors();
averageBottomTwoSensors();
// サーボ制御
if (gRunning == true) {
if (gTrackToLightEnabled == true) {
trackToLightSensors(); // 光センサーベースの自動追尾
} else {
smoothMoveTwist(); // 手動コマンド時の滑らかな移動
smoothMoveTilt();
}
}
delay(GIVE_BACK_TIME);
}
// 追尾有効化
void enableTracking() {
gRunning = true;
gServoTwist.attach(O_TWIST_SERVO_PIN);
gServoTilt.attach(O_TILT_SERVO_PIN);
digitalWrite(O_GREEN_LED_PIN, HIGH);
digitalWrite(O_RED_LED_PIN, LOW);
Serial.println("servos enabled");
}
// 追尾無効化
void disableTracking() {
gRunning = false;
gTrackToLightEnabled = false;
gServoTwist.detach();
gServoTilt.detach();
gServoTwistPositionCommand = gServoTwist.read();
gServoTwistMoveIP = false;
gServoTiltPositionCommand = gServoTilt.read();
gServoTiltMoveIP = false;
digitalWrite(O_RED_LED_PIN, HIGH);
digitalWrite(O_GREEN_LED_PIN, LOW);
Serial.println("servos disabled");
}
// 光センサー値に基づく自動追尾
void trackToLightSensors() {
float fTop, fBottom, fLeft, fRight;
int iTwistMoveCommand, iTiltMoveCommand;
int iMoveAmount = 5; // 1スキャンあたりの移動角度(GIVE_BACK_TIMEと合わせて速度決定)
iTwistMoveCommand = gServoTwist.read();
iTiltMoveCommand = gServoTilt.read();
fTop = averageTopTwoSensors();
fBottom = averageBottomTwoSensors();
fLeft = averageLeftTwoSensors();
fRight = averageRightTwoSensors();
// 水平(ツイスト)方向
if (fLeft > fRight) iTwistMoveCommand += iMoveAmount;
else if (fRight > fLeft) iTwistMoveCommand -= iMoveAmount;
// 垂直(チルト)方向
if (fTop > fBottom) iTiltMoveCommand += iMoveAmount;
else if (fBottom > fTop) iTiltMoveCommand -= iMoveAmount;
// 範囲クランプ
if (iTwistMoveCommand < 0) iTwistMoveCommand = 0;
if (iTwistMoveCommand > 179) iTwistMoveCommand = 179;
if (iTiltMoveCommand < 45) iTiltMoveCommand = 45; // 下限制限
if (iTiltMoveCommand > 135) iTiltMoveCommand = 135; // 上限制限
gServoTwist.write(iTwistMoveCommand);
gServoTilt.write(iTiltMoveCommand);
if (gVerboseDiagMode) {
Serial.print("LDRs: "); Serial.print(gLDR1); Serial.print(",");
Serial.print(gLDR2); Serial.print(","); Serial.print(gLDR3); Serial.print(",");
Serial.print(gLDR4); Serial.print(" Avgs: "); Serial.print(fTop); Serial.print(",");
Serial.print(fBottom); Serial.print(","); Serial.print(fLeft); Serial.print(",");
Serial.print(fRight); Serial.print(" Move: "); Serial.print(iTwistMoveCommand);
Serial.print(","); Serial.println(iTiltMoveCommand);
}
}
// フォトレジスタ値読み取り(0-1023)
void readPhotoResistors() {
gLDR1 = analogRead(I_LDR1_PIN);
gLDR2 = analogRead(I_LDR2_PIN);
gLDR3 = analogRead(I_LDR3_PIN);
gLDR4 = analogRead(I_LDR4_PIN);
}
// 滑らかな移動:ツイストサーボ
void smoothMoveTwist() {
int iCurrentPos = gServoTwist.read();
if (iCurrentPos == gServoTwistPositionCommand) {
gServoTwistMoveIP = false; return;
}
gServoTwistMoveIP = true;
int iMoveAmountPerScan = 1; // 1スキャンあたり1度ずつ
int iNewMoveCommand = iCurrentPos;
if (iCurrentPos < gServoTwistPositionCommand) iNewMoveCommand += iMoveAmountPerScan;
else iNewMoveCommand -= iMoveAmountPerScan;
if (iNewMoveCommand < 0) iNewMoveCommand = 0;
if (iNewMoveCommand > 179) iNewMoveCommand = 179;
gServoTwist.write(iNewMoveCommand);
if (gVerboseDiagMode) {
Serial.print("Twist move: "); Serial.print(iNewMoveCommand);
Serial.print(" target: "); Serial.println(gServoTwistPositionCommand);
}
}
// 滑らかな移動:チルトサーボ
void smoothMoveTilt() {
int iCurrentPos = gServoTilt.read();
if (iCurrentPos == gServoTiltPositionCommand) {
gServoTiltMoveIP = false; return;
}
gServoTiltMoveIP = true;
int iMoveAmountPerScan = 1;
int iNewMoveCommand = iCurrentPos;
if (iCurrentPos < gServoTiltPositionCommand) iNewMoveCommand += iMoveAmountPerScan;
else iNewMoveCommand -= iMoveAmountPerScan;
if (iNewMoveCommand < 0) iNewMoveCommand = 0;
if (iNewMoveCommand > 179) iNewMoveCommand = 179;
gServoTilt.write(iNewMoveCommand);
if (gVerboseDiagMode) {
Serial.print("Tilt move: "); Serial.print(iNewMoveCommand);
Serial.print(" target: "); Serial.println(gServoTiltPositionCommand);
}
}
// 4センサーの平均値計算
float averageTopTwoSensors() { return (gLDR1 + gLDR2) / 2.0; }
float averageBottomTwoSensors() { return (gLDR3 + gLDR4) / 2.0; }
float averageLeftTwoSensors() { return (gLDR1 + gLDR3) / 2.0; }
float averageRightTwoSensors() { return (gLDR2 + gLDR4) / 2.0; }
// シリアルコマンド処理
// 形式:上2桁がコマンドカテゴリ、下3桁が値
void processSerialMessage(int iControlCode) {
int iControlCommand = iControlCode / 1000;
int iControlValue = iControlCode % 1000;
Serial.print("control code: "); Serial.println(iControlCode);
Serial.print("command: "); Serial.println(iControlCommand);
Serial.print("value: "); Serial.println(iControlValue);
// 汎用コマンド (10xxx)
if (iControlCommand == 10) {
switch (iControlValue) {
case 0: gVerboseDiagMode = true; digitalWrite(O_BLUE_LED_PIN, HIGH); Serial.println("diagnostics ON"); break;
case 1: gVerboseDiagMode = false; digitalWrite(O_BLUE_LED_PIN, LOW); Serial.println("diagnostics OFF"); break;
case 2: reportProductInfo(); break;
case 3: digitalWrite(O_RED_LED_PIN, HIGH); Serial.println("red LED ON"); break;
case 4: digitalWrite(O_RED_LED_PIN, LOW); Serial.println("red LED OFF"); break;
case 5: digitalWrite(O_GREEN_LED_PIN, HIGH); Serial.println("green LED ON"); break;
case 6: digitalWrite(O_GREEN_LED_PIN, LOW); Serial.println("green LED OFF"); break;
case 7: digitalWrite(O_BLUE_LED_PIN, HIGH); Serial.println("blue LED ON"); break;
case 8: digitalWrite(O_BLUE_LED_PIN, LOW); Serial.println("blue LED OFF"); break;
case 9: Serial.print("LDR1: "); Serial.println(gLDR1); break;
case 10: Serial.print("LDR2: "); Serial.println(gLDR2); break;
case 11: Serial.print("LDR3: "); Serial.println(gLDR3); break;
case 12: Serial.print("LDR4: "); Serial.println(gLDR4); break;
case 13: enableTracking(); break;
case 14: disableTracking(); break;
case 19: if (gRunning && !gServoTwistMoveIP && !gServoTiltMoveIP) { gServoTwistPositionCommand=90; gServoTiltPositionCommand=90; Serial.println("servos to 90deg"); } break;
case 21: if (gRunning) { gTrackToLightEnabled=true; Serial.println("light tracking ON"); } break;
case 22: gTrackToLightEnabled=false; Serial.println("light tracking OFF"); break;
default: Serial.print("invalid value: "); Serial.println(iControlValue);
}
}
// ツイストサーボ直接指令 (11xxx)
else if (iControlCommand == 11) {
if (iControlValue >= 0 && iControlValue <= 179) {
if (gRunning && !gServoTwistMoveIP) {
gServoTwistPositionCommand = iControlValue;
gServoTwistMoveIP = true;
Serial.print("Twist cmd: "); Serial.println(gServoTwistPositionCommand);
}
} else { Serial.print("invalid value: "); Serial.println(iControlValue); }
}
// チルトサーボ直接指令 (12xxx)
else if (iControlCommand == 12) {
if (iControlValue >= 0 && iControlValue <= 179) {
if (gRunning && !gServoTiltMoveIP) {
gServoTiltPositionCommand = iControlValue;
gServoTiltMoveIP = true;
Serial.print("Tilt cmd: "); Serial.println(gServoTiltPositionCommand);
} else {
Serial.print("tilt not ready: "); Serial.print(gRunning); Serial.print(","); Serial.println(gServoTiltMoveIP);
}
} else { Serial.print("invalid value: "); Serial.println(iControlValue); }
}
Serial.println("-----");
}
// 起動シーケンス
void startup() {
int iDelay = 500;
reportProductInfo(); delay(iDelay);
processSerialMessage(10003); delay(iDelay); // 赤LED ON
processSerialMessage(10004); processSerialMessage(10005); delay(iDelay); // 赤OFF、緑ON
processSerialMessage(10006); processSerialMessage(10007); delay(iDelay); // 緑OFF、青ON
processSerialMessage(10008); processSerialMessage(10009); processSerialMessage(10010); processSerialMessage(10011); processSerialMessage(10012); delay(iDelay); // LDR値表示
enableTracking(); delay(iDelay);
processSerialMessage(10019); delay(iDelay); // ホームポジション(90,90)
disableTracking();
Serial.println("startup sequence completed");
}
// 製品情報出力
void reportProductInfo() {
Serial.println("q version 2");
Serial.println("tucson, arizona usa");
Serial.println("may 2018");
Serial.print("checksum "); Serial.println("58BA-D969-2F82-08FD-2078-2777-396D-E1AA");
}
3Dモデル・筐体
回路図
製造プロセス
- ソーラー暖房システム:パッシブからアクティブまで、持続可能な住宅暖房ソリューション
- 太陽電池:初期実験から現代フォトボルタイクスまで
- Arduinoで実現する低コスト・リアルタイム太陽光パネル監視システム
- スマートブラインド:Arduinoとソーラー電源で実現する自動光制御
- IoT対応ソーラートラッカー:Arduinoによるスマートエネルギー管理
- SmartPostBox: IoT対応スマートメールボックスシステム (Sigfox + Arduino MKR Fox 1200)
- デュアルアクシスソーラートラッカー:自動/手動切替搭載の高効率太陽追尾システム
- ポータブルソーラートラッカースマホ充電器:Arduinoとサーボで太陽追尾
- 太陽エネルギー活用ガイド:クリーンで信頼性の高い電力へ
- ソーラーパネル品質管理:最適性能を引き出す必須チェックポイント
- ソーラーパネル製造の全工程:高品質太陽光発電へのステップバイステップガイド