Arduinoロボットアームの作り方:サーボの選び方、トルク計算、PCA9685とBluetooth
Arduinoロボットアームをステップごとに作ります。MDFまたは3Dプリントのフレーム、MG996Rのトルク計算、ポテンショメーターとジョイスティックでの操作、記録と再生、PCA9685とBluetoothのコードまで解説します。
Arduinoロボットアームは、サーボモーターで動く関節によって物をつかんで持ち上げ、向きを変えて別の場所に置ける、卓上サイズのロボットです。土台(ベース)、肩、ひじ、手首、グリッパーで構成され、機構設計、モーター選び、電源管理、プログラミングを一つのプロジェクトで学べます。しかも、Arduino Uno、数個のサーボ、MDFまたは3Dプリントの部品があれば家でも作れ、工場で働く産業用ロボットアームの小さな模型を自分の机の上で動かせます。
このガイドでは、4軸と6軸のロボットアームを最初から最後まで扱います。フレームの材料選び、SG90・MG90S・MG996Rから正しいサーボを選ぶためのトルク計算、5 Vの外部電源、ポテンショメーターとジョイスティックによる操作、動きの記録と再生、16チャンネルのPCA9685サーボドライバ、スマートフォンからのBluetooth操作、そして最もよくある二つのトラブルである震えとリセットの対策です。サーボの仕組みがよくわからない場合は、先にサーボモーターとは?を読んでおくと安心です。



ロボットアームの仕組みと、必要な軸の数
ロボットアームは人の腕にたとえるとわかりやすくなります。関節ごとにサーボモーターがあり、Arduinoは各サーボに「この角度へ動け」と指示します。サーボは内部のポテンショメーターのおかげで軸の位置を把握しており、負荷がかかってもその角度を保ちます。関節の角度を組み合わせて変えることで、グリッパーを狙った位置まで運びます。アームが行える独立した動きの数を自由度(DOF)と呼び、ふだんは単に「軸」と呼びます。
| 関節 | 動き | 負荷 | おすすめのサーボ |
|---|---|---|---|
| ベース(腰) | アームを左右に旋回させる | 持ち上げはしないが、アーム全体を支える | MG996R+ベアリング |
| 肩 | アームを前後に上げ下げする | 最も負荷が大きい関節 | MG996Rまたは20 kg·cmクラスのサーボ |
| ひじ | 前腕を曲げる | 中〜高 | MG996R |
| 手首(ピッチ) | グリッパーの角度を決める | 低 | MG90S |
| 手首の回転(ロール) | グリッパーを軸まわりに回す | ごく低い | MG90SまたはSG90 |
| グリッパー | 物をつかんで離す | つかむ力 | SG90またはMG90S |
市販の「6軸ロボットアーム」キットの多くはサーボを6個使いますが、グリッパーは軸に数えないため、厳密には5自由度のアームにグリッパーが付いたものです。初めてのアームには4軸構成(ベース、肩、ひじ、グリッパー)が最適です。サーボ4個、ポテンショメーター4個、Arduino Unoがあれば足り、配線も単純で、トルク不足もめったに起きません。手首の関節は、グリッパーを水平に保ちたいときや、いろいろな角度から物をつかみたいときに役立ちます。サーボが5〜6個になると、PCA9685ドライバはほぼ必須になります。
部品リスト
| 部品 | 4軸アーム | サーボ6個のアーム | メモ |
|---|---|---|---|
| コントローラー | Arduino UnoまたはNano | Arduino Uno、NanoまたはESP32 | NanoはA6とA7の入力が多い分、ポテンショメーターを増やせる |
| サーボモーター | SG90またはMG90S×4 | MG996R×3+MG90S×3 | 下のトルク計算で選んだサーボを確認する |
| サーボドライバ | 不要 | PCA9685 16チャンネルモジュール | I2Cで信号線は2本だけ |
| 電源 | 5 V 3 AのACアダプター | 5〜6 VのACアダプター、5 A以上 | サーボは絶対にArduinoから給電しない |
| コンデンサ | 470〜1000 µF、16 V | 1000〜2200 µF、16 V | サーボの電源ラインに入れる。極性に注意 |
| 操作部 | 10 kΩポテンショメーター×4 | ジョイスティックモジュール×2またはHC-05 | 両方使ってもよい |
| ボタン | 2個(記録、再生) | ジョイスティックのボタンで足りる | INPUT_PULLUPなら追加の抵抗は不要 |
| 機構部品 | 3 mmのMDFまたは3Dプリント部品 | 4 mmのMDF、3Dプリントまたはアルミブラケット | 608ベアリング、M3ボルト、ゆるみ止めナット |
機構設計:MDFか3Dプリントか
| 材料 | 長所 | 短所 | 向いている用途 |
|---|---|---|---|
| 3〜4 mmのMDF(レーザーカット) | 安くて軽く、ねじ止めも接着も簡単 | 湿気に弱く、ねじ穴が時間とともに広がる | MeArm風の最初のアーム、学校のプロジェクト |
| 3 mmのアクリル | 見た目がよく、湿気の影響を受けない | 割れやすく、ねじを締めるとひびが入ることがある | 展示用・見せる作品 |
| 3Dプリント(PLA / PETG) | 複雑な形やサーボにぴったりのポケットを作れる | 印刷に時間がかかる。PLAは熱や常時かかる負荷で変形(クリープ)する | 自分で設計するアーム。負荷のかかる部品はPETG |
| アルミブラケットキット | 非常に頑丈で、MG996R用の既製部品がある | 重くて高価。肩への負荷が増える | 重い物を持ち上げるサーボ6個のアーム |
| 段ボール、アイスの棒 | ほぼ無料で、試作が速い | 強度が低く、関節にガタが出る | 寸法の確認、子どもとの初めての工作 |
- アームは短く軽く:肩にかかる負荷は距離に比例して大きくなります。アームを30 cmではなく24 cmにすると、肩のトルクは約5分の1減ります。
- 重いサーボはベースの近くに:ひじのサーボを肩の位置まで下げ、プッシュロッド(平行リンク)でひじを動かすのは、MeArmなどのアームで使われている賢い工夫です。肩がそのサーボの重さを持ち上げなくて済みます。
- ベースにはベアリングを:アーム全体の重さをベースサーボの軸にぶら下げないでください。608ベアリング(内径8 mm、外径22 mm、厚さ7 mm。スケートボードでおなじみのベアリング)や回転台用ベアリング(ターンテーブル)が荷重を受け、サーボは回すだけになります。
- 関節は両側で支える:サーボがホーン側で関節を支えている間、反対側にはM3ボルトとゆるみ止めナットで回転軸を作ります。こうするとサーボの軸に曲げの力がかからず、ギアの寿命がずっと長くなります。
- 金属ホーンを使う:MG996Rにはそれぞれ25歯(25T)の金属製丸ホーンを取り付け、中央のねじで軸に固定します。プラスチックのホーンは負荷の大きい関節で滑り、スプライン(歯)がつぶれてしまいます。
- グリッパーの指にゴムを:指先に薄いゴムを貼ると、少ない力でも滑らずに物をつかめます。
サーボ選び:SG90、MG90S、MG996Rのどれにする?
| 型番 | トルク | 速度(60°あたり) | 重さ | ギア | ストール電流 | ロボットアームでの役割 |
|---|---|---|---|---|---|---|
| SG90 | 1.8 kg·cm (4.8 V) | 0.10 s (4.8 V) | 9 g | プラスチック | 約0.6〜0.75 A | グリッパー、軽い手首、小型の4軸アーム |
| MG90S | 1.8 kg·cm (4.8 V), 2.2 kg·cm (6 V) | 0.10 s (4.8 V) | 13.4 g | 金属 | 約0.7〜1 A | 手首、手首の回転、グリッパー |
| MG996R | 9.4 kg·cm (4.8 V), 11 kg·cm (6 V) | 0.17 s (4.8 V) | 55 g | 金属 | 2.5 A (6 V) | ベース、肩、ひじ |
| DS3218 | 19 kg·cm (5 V), 21.5 kg·cm (6.8 V) | 0.16 s (5 V) | 60 g | 金属 | 約2〜3 A | 長いアームや重いアームの肩 |
表のトルクはストールトルクです。これは軸が止まる直前にサーボが出せる最大の力で、連続して使える値ではありません。サーボはこの限界に近づくほど電流がストール電流に向かって増え、発熱して震え始めます。実用上のルールは、関節に必要なトルクをサーボのストールトルクの半分以下に収めることです。電圧も効きます。MG996Rは4.8 Vで9.4 kg·cm、6 Vで11 kg·cmを出すので、同じサーボでも電源電圧だけで約17%強くなります。そのためMG996Rのアームには6 V電源が理にかなっていますが、SG90とMG90Sにとって6 Vは上限です。MG996Rの配線と限界についてはMG996Rサーボモーターのガイドも参考にしてください。

トルク計算:肩のサーボはこのアームを持ち上げられるか
サーボが持ち上げなければならないトルクは、各部品の質量に、関節の軸からの水平距離を掛けた値の合計です。トルク(kg·cm)=Σ質量(kg)×距離(cm)。最も厳しいのはアームを水平に伸ばしたときで、すべての距離が最大になります。アームが真上を向いていれば、このてこの長さはほぼゼロになります。ニュートンメートルに換算するには0.098を掛けます(1 kg·cm ≈ 0.098 N·m)。例として、肩からひじまで12 cm、ひじから手首まで12 cm、手首からグリッパーの先まで6 cm、ひじにMG996Rを使った卓上アームで、グリッパーが50 gの物を持っているときの肩の負荷を計算してみましょう。
| 部品 | 質量 | 肩の軸からの距離 | トルク |
|---|---|---|---|
| 上腕の部品(MDFとボルト) | 40 g | 6 cm(重心) | 0.04 × 6 = 0.24 kg·cm |
| ひじのサーボ(MG996R) | 55 g | 12 cm | 0.055 × 12 = 0.66 kg·cm |
| 前腕の部品 | 30 g | 18 cm | 0.03 × 18 = 0.54 kg·cm |
| 手首のサーボ+グリッパー(MG90S、SG90と部品) | 50 g | 25 cm | 0.05 × 25 = 1.25 kg·cm |
| つかんでいる物 | 50 g | 30 cm | 0.05 × 30 = 1.50 kg·cm |
| 合計 | 225 g | — | 4.19 kg·cm(≈ 0.41 N·m) |
静止しているだけで、このアームの肩には4.19 kg·cmが必要です。加速やブレーキで生じる余分な負荷をまかない、サーボをストールから十分に遠ざけるために2倍します。つまり8.4 kg·cmです。したがって肩には最低でもMG996R(4.8 Vで9.4 kg·cm、6 Vで11 kg·cm)が必要で、SG90やMG90Sの1.8〜2.2 kg·cmではこのアームはびくともしません。ひじについて同じ計算をすると、前腕(0.03 × 6)、手首とグリッパー(0.05 × 13)、物(0.05 × 18)の合計は1.73 kg·cm、安全率を掛けると3.5 kg·cmなので、ひじにもMG996Rが必要です。手首に残るのはグリッパーと物だけなので負荷は約0.4 kg·cmで、MG90Sで十分です。同じ計算で、アームが運べる最も重い物もわかります。物の質量を増やして合計を計算し直し、サーボのトルクの半分を超えないようにします。
電源:サーボをArduinoから給電しない理由
ロボットアームのトラブルの大半は電源が原因です。MG996Rは動いている間に0.5〜0.9 A、ストール時には最大2.5 Aを流します。小さなSG90でもストール時には0.6 Aを超えることがあります。Arduino UnoをUSBで動かしている場合、5Vピンから取り出せる電流の合計はUSBの上限である約500 mAで、その一部はボード自身が使います。大きなサーボ3個が同時に動き出すと電圧が落ち込み、マイコンがリセットされて、アームは初期姿勢に跳ね戻ります。対策は、サーボを別の5〜6 V電源から給電し、すべてのGNDをつなぐことです。Arduinoは引き続きUSBや専用のACアダプターから給電して構いません。電源の選択肢はArduinoの電源のガイドを、電池で動かすアームの稼働時間の目安は電池寿命計算ツールを使ってみてください。
| アームの種類 | 推定電流 | おすすめの電源 |
|---|---|---|
| SG90またはMG90S×4の小型アーム | 動作中0.5〜1 A、ピーク2〜3 A | 5 V 3 AのACアダプターまたはLM2596モジュール(最大3 A、放熱板なしで約2 A) |
| MG996R×3+MG90S×3のサーボ6個のアーム | 動作中2〜3 A、全部ストールすると8〜10 A | 5〜6 VのスイッチングACアダプター、5 A以上(余裕を持たせるなら10 A) |
| 持ち運べるアーム | アームによる | 2S LiPo(7.4 V)+6 Vに設定した5 A以上の降圧モジュール(UBEC) |
| 避けるべき電源 | — | 9 Vの角形電池(電流を出せない)、Arduinoの5Vピン、MG996Rのアームに単3アルカリ電池4本 |
4軸アームの配線
下の表は、ポテンショメーターで操作する4軸アームのArduino UnoとRaspberry Pi Picoの配線です。このガイドのコードはこのピンを使います。UnoのServoライブラリはどのデジタルピンでもサーボを動かせますが、Timer1を使うため、サーボを接続している間はピン9と10でanalogWriteが使えなくなります。ボードのピン配置はピン配置ツールで確認できます。
| 接続 | Arduino Uno | Raspberry Pi Pico |
|---|---|---|
| ベースサーボの信号(オレンジの線) | D3 | GP10 |
| 肩サーボの信号 | D5 | GP11 |
| ひじサーボの信号 | D6 | GP12 |
| グリッパーサーボの信号 | D9 | GP13 |
| ポテンショメーター1〜3の中央端子(ベース、肩、ひじ) | A0, A1, A2 | GP26, GP27, GP28 |
| ポテンショメーター4の中央端子(グリッパー) | A3 | なし。グリッパーはGP14のボタンで操作 |
| ポテンショメーターの両端の足 | 5VとGND | 3V3とGND |
| 記録ボタンと再生ボタン(反対側はGNDへ) | D7とD8 | — |
| サーボの赤い線(+) | 外部5〜6 V電源のプラス | 外部5〜6 V電源のプラス |
| サーボの茶色の線(−) | 外部電源のマイナス+ArduinoのGND | 外部電源のマイナス+PicoのGND |

組み立て前のキャリブレーション:サーボを中央に合わせる
- ホーンを取り付ける前に、下のコードを書き込みます。4個のサーボが順番に90度へ動き、そこで待機します。
- アームを中立の姿勢(たとえば上腕は垂直、前腕は水平、グリッパーは半開き)に保ち、各ホーンをその姿勢にいちばん近いスプラインの歯に差し込んで、中央のねじで固定します。
- コードのCENTER_ONLYをfalseにし、TESTで関節を一つ選んで、A0のポテンショメーターをゆっくり回します。
- シリアルモニタで角度を見ながら、関節がどこにもぶつからず、ケーブルも引っ張らずに届く最小と最大の角度を書き留めます。
- グリッパーの閉じ角度は、指がちょうど触れる位置に設定します。さらに強く握ろうとし続けるサーボはストールしたままになり、発熱します。
- 求めた値を、以降のスケッチのMIN_AとMAX_Aの配列に入力します。
1#include <Servo.h>
2
3const byte SERVO_PIN[4] = {3, 5, 6, 9}; // taban, omuz, dirsek, kıskaç / base, shoulder, elbow, gripper
4const byte TEST = 1; // denenecek servo (0–3) / servo under test (0–3)
5const bool CENTER_ONLY = true; // true: hepsi 90°'de bekler / true: all wait at 90°
6
7Servo servo[4];
8
9void setup() {
10 Serial.begin(9600);
11 for (byte i = 0; i < 4; i++) {
12 servo[i].write(90); // önce açıyı ver / set the angle first
13 servo[i].attach(SERVO_PIN[i]); // sonra bağla: zıplamaz / then attach: no jump
14 delay(300); // akım darbelerini ayır / spread the current surges
15 }
16}
17
18void loop() {
19 if (CENTER_ONLY) return; // korna takarken burada kal / stay here while fitting horns
20 int angle = map(analogRead(A0), 0, 1023, 0, 180); // A0'daki pot / pot on A0
21 servo[TEST].write(angle);
22 Serial.print(F("Servo "));
23 Serial.print(TEST);
24 Serial.print(F(" aci / angle: "));
25 Serial.println(angle); // sınırları buradan oku / read the limits here
26 delay(50);
27}このコードには小さいけれど大切な工夫があります。attach()の前にservo.write(90)を呼んでいるので、サーボは最初のパルスから指定の角度を受け取ります。順番を逆にすると、ライブラリはまずデフォルトの1500 µsのパルス(約90°)を送ります。開始角度が90でなければ、電源投入時にアームがいったん中央へ跳ね、そこから目標へ動きます。サーボを300 msずつ間をあけて一つずつ起動しているのは、電源投入時の突入電流を分散させるためでもあります。
ポテンショメーターでロボットアームを操作する
いちばん直感的な操作方法は、関節ごとにポテンショメーターを一つずつ使うやり方です。ポテンショメーターの軸がどの角度にあっても関節が同じ角度へ動くので、位置制御になります。ポテンショメーターをアームの小さな縮尺模型(段ボールや3Dプリントで、同じ比率)に組み込めば、模型を手で動かすと大きなアームがその動きを1対1でまねします。ただし、アナログの読み取り値はもともと数カウントほど揺れ、その揺れがサーボの震えとして現れます。コードにはその対策が二つ入っています。
1#include <Servo.h>
2
3const byte SERVO_PIN[4] = {3, 5, 6, 9}; // taban, omuz, dirsek, kıskaç / base, shoulder, elbow, gripper
4const byte POT_PIN[4] = {A0, A1, A2, A3}; // her eklem için bir pot / one pot per joint
5const byte MIN_A[4] = {0, 15, 20, 40}; // kalibrasyonda bulduğun alt sınırlar / lower limits from calibration
6const byte MAX_A[4] = {180, 165, 160, 110}; // üst sınırlar / upper limits
7
8Servo servo[4];
9float smooth[4]; // süzülmüş pot değeri / filtered pot value
10int lastAngle[4]; // son yazılan açı / last written angle
11
12void setup() {
13 for (byte i = 0; i < 4; i++) {
14 smooth[i] = analogRead(POT_PIN[i]);
15 lastAngle[i] = map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
16 servo[i].write(lastAngle[i]); // önce açı, sonra attach / angle first, then attach
17 servo[i].attach(SERVO_PIN[i]);
18 delay(300); // servolar sırayla uyansın / wake the servos one by one
19 }
20}
21
22void loop() {
23 for (byte i = 0; i < 4; i++) {
24 smooth[i] += (analogRead(POT_PIN[i]) - smooth[i]) * 0.2; // yumuşatma filtresi / low-pass filter
25 int angle = map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
26 if (abs(angle - lastAngle[i]) >= 2) { // ölü bant: küçük gürültüyü yok say / deadband: ignore small noise
27 servo[i].write(angle);
28 lastAngle[i] = angle;
29 }
30 }
31 delay(15);
32}smooth配列はシンプルなローパスフィルタです。ループごとに新しい読み取り値の20%だけを加えるので、急な跳びはなめらかになりつつ、アームは素早く反応します。0.2を0.1に変えると動きはさらになめらかになりますが、遅れが大きくなります。二つ目の対策は不感帯(デッドバンド)です。新しい角度が最後に書き込んだ角度と2度以上違わない限りサーボには何も送らないので、アームが止まっているときの震えがなくなります。map()がポテンショメーターの範囲を各関節の安全な限界へ直接変換するので、ポテンショメーターをいっぱいまで回しても関節が機械的なストッパーにぶつかることはありません。
動きを記録して再生する
ロボットアームを本当の意味でロボットにするのは、教えた動きを自分で繰り返すことです。方法は簡単です。ポテンショメーターでアームをある姿勢に動かして記録ボタンを押すと、Arduinoが4個の関節すべての角度を保存します。いくつかの姿勢を記録したら再生ボタンを押します。アームはそれらを順番に繰り返し、姿勢と姿勢の間をなめらかにつなぎます。姿勢はArduino Unoの1024バイトのEEPROMに書き込まれるので、電源を切っても消えません。姿勢一つで4バイトなので、100姿勢を記録しても401バイトしか使いません。
1#include <Servo.h>
2#include <EEPROM.h>
3
4const byte SERVO_PIN[4] = {3, 5, 6, 9}; // taban, omuz, dirsek, kıskaç / base, shoulder, elbow, gripper
5const byte POT_PIN[4] = {A0, A1, A2, A3};
6const byte MIN_A[4] = {0, 15, 20, 40}; // güvenli sınırlar / safe limits
7const byte MAX_A[4] = {180, 165, 160, 110};
8const byte BTN_REC = 7, BTN_PLAY = 8, LED = 13; // butonların diğer ucu GND'de / buttons go to GND
9const byte MAX_STEPS = 100; // 100 adım x 4 bayt = 400 bayt / 100 steps x 4 bytes
10
11Servo servo[4];
12float smooth[4]; // süzülmüş pot değeri / filtered pot value
13byte pos[4]; // servoların şu anki açısı / current servo angles
14byte stepCount; // kayıtlı adım sayısı / number of saved steps
15
16bool pressed(byte pin) { // basılıyken true / true while pressed
17 return digitalRead(pin) == LOW;
18}
19
20byte potAngle(byte i) {
21 smooth[i] += (analogRead(POT_PIN[i]) - smooth[i]) * 0.2; // yumuşatma / smoothing
22 return map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
23}
24
25void blink(byte n) {
26 for (byte k = 0; k < n; k++) {
27 digitalWrite(LED, HIGH); delay(80);
28 digitalWrite(LED, LOW); delay(80);
29 }
30}
31
32// Tüm eklemleri aynı anda hedefe götürür / moves all joints to the target together
33void moveTo(const byte target[4], byte msPerDegree) {
34 byte start[4];
35 int steps = 0;
36 for (byte i = 0; i < 4; i++) {
37 start[i] = pos[i];
38 int d = abs((int)target[i] - (int)start[i]);
39 if (d > steps) steps = d; // en uzun hareket / longest move
40 }
41 for (int s = 1; s <= steps; s++) {
42 for (byte i = 0; i < 4; i++) {
43 pos[i] = start[i] + (long)((int)target[i] - (int)start[i]) * s / steps; // ara açı / in-between angle
44 servo[i].write(pos[i]);
45 }
46 delay(msPerDegree);
47 }
48}
49
50void saveStep() {
51 if (stepCount >= MAX_STEPS) { blink(3); return; } // hafıza dolu / memory full
52 for (byte i = 0; i < 4; i++) EEPROM.update(1 + stepCount * 4 + i, pos[i]);
53 stepCount++;
54 EEPROM.update(0, stepCount); // 0. bayt adım sayısını tutar / byte 0 holds the step count
55 blink(1);
56}
57
58void playBack() {
59 byte target[4];
60 for (byte s = 0; s < stepCount; s++) {
61 for (byte i = 0; i < 4; i++) target[i] = EEPROM.read(1 + s * 4 + i);
62 moveTo(target, 12); // derece başına 12 ms / 12 ms per degree
63 delay(400); // her pozda kısa bekleme / short pause at each pose
64 if (pressed(BTN_PLAY)) break; // oynatırken basınca durur / press again to stop
65 }
66}
67
68void syncToPots() { // potların konumuna yumuşakça dön / ease back to the pots
69 byte target[4];
70 for (byte i = 0; i < 4; i++) {
71 smooth[i] = analogRead(POT_PIN[i]);
72 target[i] = map((int)smooth[i], 0, 1023, MIN_A[i], MAX_A[i]);
73 }
74 moveTo(target, 12);
75}
76
77void setup() {
78 pinMode(BTN_REC, INPUT_PULLUP);
79 pinMode(BTN_PLAY, INPUT_PULLUP);
80 pinMode(LED, OUTPUT);
81 stepCount = EEPROM.read(0);
82 if (stepCount > MAX_STEPS) stepCount = 0; // boş EEPROM 255 okur / a blank EEPROM reads 255
83 for (byte i = 0; i < 4; i++) {
84 smooth[i] = analogRead(POT_PIN[i]);
85 pos[i] = potAngle(i);
86 servo[i].write(pos[i]); // önce açı, sonra attach / angle first, then attach
87 servo[i].attach(SERVO_PIN[i]);
88 delay(300);
89 }
90}
91
92void loop() {
93 // Kayıt: kısa bas = pozu kaydet, 2 sn basılı tut = hepsini sil / Record: short press = save pose, hold 2 s = erase all
94 if (pressed(BTN_REC)) {
95 unsigned long t0 = millis();
96 while (pressed(BTN_REC) && millis() - t0 < 2000) {}
97 if (millis() - t0 >= 2000) {
98 stepCount = 0;
99 EEPROM.update(0, 0);
100 blink(5);
101 } else {
102 saveStep();
103 }
104 while (pressed(BTN_REC)) {} // bırakılmasını bekle / wait for release
105 delay(50); // buton arkı / debounce
106 }
107
108 // Oynat: kaydedilen pozları sırayla tekrarla / play: repeat the saved poses in order
109 if (pressed(BTN_PLAY) && stepCount > 0) {
110 while (pressed(BTN_PLAY)) {}
111 delay(50);
112 digitalWrite(LED, HIGH);
113 playBack();
114 while (pressed(BTN_PLAY)) {}
115 syncToPots();
116 digitalWrite(LED, LOW);
117 }
118
119 // Normal mod: kolu potlarla sür / normal mode: drive the arm with the pots
120 for (byte i = 0; i < 4; i++) {
121 byte a = potAngle(i);
122 if (abs((int)a - (int)pos[i]) >= 2) {
123 pos[i] = a;
124 servo[i].write(a);
125 }
126 }
127 delay(15);
128}| 操作 | やること | アームの動作 |
|---|---|---|
| 姿勢を保存 | 記録ボタン(D7)を短く押す | LEDが1回点滅し、姿勢がEEPROMに書き込まれる |
| 記録を消去 | 記録ボタンを2秒間押し続ける | LEDが5回点滅し、ステップ数がリセットされる |
| 再生 | 再生ボタン(D8)を押して離す | LEDが点灯し、姿勢がなめらかなつなぎで順番に再生される |
| 再生を止める | 再生中に再生ボタンを押し続ける | アームは今の姿勢を終えてから止まる |
| 通常モードに戻る | 再生が終わったら何もしない | アームはポテンショメーターが示す位置へゆっくり戻り、LEDが消える |
moveTo()関数はこのスケッチの心臓部です。移動量がいちばん大きい関節を見つけ、その数だけ動きをステップに分け、ステップごとにすべての関節を比例して進めます。こうするとすべての関節が同時に目標へ到着し、関節を一つずつカクカク動かすロボットらしい動きではなく、流れるような動きになります。1度あたり12 msなので、90度の移動には約1.1秒かかります。速く再生したい場合はこの値を小さくできますが、MG996Rは無負荷でも60度回るのに0.17秒、1度あたり約3 msかかります。それより小さくするとサーボが指令に追いつけず、動きがぎくしゃくします。
PCA9685サーボドライバ:サーボ6個と震えのない信号
サーボが5〜6個になったり、Bluetoothなどのモジュールを追加したりするなら、PCA9685モジュールを使うとずっと楽になります。このボードは16チャンネル・12ビットのPWM発生器で、I2Cを使ってたった2本の線でArduinoと通信し、プロセッサとは独立に自分のクロックでサーボのパルスを作ります。デフォルトのアドレスは0x40で、A0〜A5のはんだジャンパーでアドレスを変えれば最大62枚、つまりサーボ992個までつなげられます。50 Hzでは20 msの周期が4096ステップに分かれるので、1ステップは約4.9 µsです。544〜2400 µsの範囲は約380ステップになり、分解能はおよそ0.5度です。
| PCA9685のピン | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| GND | GND | GND | GND |
| VCC(チップの電源のみ) | 5V | 3V3 | 3V3 |
| SDA | A4 | GPIO 21 | GP4 |
| SCL | A5 | GPIO 22 | GP5 |
| V+端子(サーボの電源) | 外部5〜6 V | 外部5〜6 V | 外部5〜6 V |
| OE | 未接続のまま | 未接続のまま | 未接続のまま |
| サーボ用ヘッダー0〜5 | ベース、肩、ひじ、手首、手首の回転、グリッパー | 同じ順番 | 同じ順番 |
ジョイスティックでロボットアームを操作する
ジョイスティックはポテンショメーターとは動きが違い、手を離すとばねで中央に戻ります。角度をジョイスティックの位置に直接結びつけると、手を離した瞬間に関節が90度へ跳ねてしまいます。正しいのは速度制御です。ジョイスティックを大きく倒すほど関節が速く回り、手を離すと関節はその場にとどまります。下のコードでは2本のジョイスティックで4個の関節を動かします(左のジョイスティックでベースと肩、右のジョイスティックでひじと手首)。左のジョイスティックのボタンでグリッパーを開閉し、右のボタンでアームをホーム姿勢に戻します。左のジョイスティックのVRxとVRyの出力をA0とA1に、右のジョイスティックのVRyとVRxの出力をA2とA3に、SWピンをD2とD4につなぎます。ジョイスティックはArduinoの5Vピンから給電して構いません。コードをコンパイルするには、ライブラリマネージャでAdafruit PWM Servo Driver Libraryをインストールし、依存ライブラリのAdafruit BusIOも一緒にインストールします。

1#include <Wire.h>
2#include <Adafruit_PWMServoDriver.h>
3
4Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40); // varsayılan adres / default address
5
6// Kanal: 0 taban, 1 omuz, 2 dirsek, 3 bilek, 4 bilek dönüşü, 5 kıskaç / Channel: 0 base, 1 shoulder, 2 elbow, 3 wrist, 4 wrist roll, 5 gripper
7const byte N = 6;
8const float MIN_A[N] = {0, 15, 20, 10, 0, 40}; // güvenli sınırlar / safe limits
9const float MAX_A[N] = {180, 165, 160, 170, 180, 110};
10const float HOME[N] = {90, 90, 90, 90, 90, 40}; // başlangıç pozu, kıskaç açık / home pose, gripper open
11const byte JOY[4] = {A0, A1, A2, A3}; // J1 X, J1 Y, J2 Y, J2 X → kanal / channel 0, 1, 2, 3
12const int8_t DIR[4] = {1, -1, 1, 1}; // ters giden ekseni -1 yap / set -1 to invert an axis
13const byte BTN_GRIP = 2, BTN_HOME = 4; // joystick SW pinleri / joystick SW pins
14const float MAX_SPEED = 90; // en yüksek hız, derece/sn / top speed, degrees/s
15const float DT = 0.02; // 20 ms döngü / 20 ms loop
16const int DEADZONE = 60; // ortadaki ölü bölge / dead zone around center
17
18float target[N], current[N];
19bool gripClosed = false;
20
21void writeServo(byte ch, float a) {
22 // Servo.h ile aynı 544–2400 µs aralığı / same 544–2400 µs range as Servo.h
23 pca.writeMicroseconds(ch, (uint16_t)(544 + a * (2400 - 544) / 180.0));
24}
25
26bool newPress(byte pin, byte k) { // her basışta bir kez true / true once per press
27 static bool prev[2] = {HIGH, HIGH};
28 bool now = digitalRead(pin);
29 bool edge = (prev[k] == HIGH && now == LOW);
30 prev[k] = now;
31 return edge;
32}
33
34void setup() {
35 pinMode(BTN_GRIP, INPUT_PULLUP);
36 pinMode(BTN_HOME, INPUT_PULLUP);
37 pca.begin();
38 pca.setOscillatorFrequency(27000000); // çoğu modülde ~27 MHz / ~27 MHz on most boards
39 pca.setPWMFreq(50); // servolar için 50 Hz / 50 Hz for servos
40 Wire.setClock(400000); // hızlı I2C / fast I2C
41 for (byte i = 0; i < N; i++) {
42 target[i] = current[i] = HOME[i];
43 writeServo(i, HOME[i]);
44 delay(300); // servolar sırayla uyansın / wake servos one by one
45 }
46}
47
48void loop() {
49 static unsigned long last = 0;
50 if (millis() - last < 20) return; // 50 Hz güncelleme / 50 Hz update
51 last = millis();
52
53 // Joystick hızı belirler, konumu değil / the joystick sets speed, not position
54 for (byte i = 0; i < 4; i++) {
55 int v = analogRead(JOY[i]) - 512;
56 if (abs(v) > DEADZONE) {
57 target[i] += DIR[i] * (v / 512.0) * MAX_SPEED * DT;
58 target[i] = constrain(target[i], MIN_A[i], MAX_A[i]);
59 }
60 }
61 if (newPress(BTN_GRIP, 0)) { // kıskaç aç/kapa / toggle gripper
62 gripClosed = !gripClosed;
63 target[5] = gripClosed ? MAX_A[5] : MIN_A[5];
64 }
65 if (newPress(BTN_HOME, 1)) { // başlangıç pozuna dön / go to home pose
66 for (byte i = 0; i < N; i++) target[i] = HOME[i];
67 gripClosed = false;
68 }
69
70 // Her eklem hedefe hız sınırıyla gider / each joint follows its target with a speed limit
71 for (byte i = 0; i < N; i++) {
72 float d = constrain(target[i] - current[i], -MAX_SPEED * DT, MAX_SPEED * DT);
73 if (d != 0) {
74 current[i] += d;
75 writeServo(i, current[i]);
76 }
77 }
78}DEADZONEは、中央で490〜530のような小さなずれが読み取られても、アームが勝手にじわじわ動かないようにします。MAX_SPEEDはジョイスティックをいっぱいまで倒したときの速度(1秒あたりの度数)を決めます。最初は毎秒90度が安全な値です。関節がジョイスティックと逆に動く場合は、配線を入れ替えずにDIR配列の該当する値を-1にしてください。コードは関節ごとに目標位置(target)と実際の位置(current)を持っています。実際の位置は速度制限付きで目標に近づくので、ホームボタンを押したときもアームはなめらかに戻ります。このスケッチでは手首の回転は90度で固定です。Bluetoothの章のRコマンドで操作できます。この止まらないタイミング処理の手法はmillis()とdelay()の違いで詳しく解説しています。
スマートフォンからBluetoothで操作する(HC-05)
HC-05モジュールは、スマートフォンとArduinoの間に無線のシリアル通信路を作ります。TXDピンはArduinoのD10へ、RXDピンは分圧回路を通してD11へつなぎます。D11 → 1 kΩ → RXD → 2 kΩ → GNDです。分圧回路はArduinoの5 Vの信号を、HC-05が想定する約3.3 Vまで下げます。モジュールのデフォルトの通信速度は9600ボーで、ペアリングのPINはたいてい1234です。スマートフォンからはシンプルな文字のコマンドを送ります。英字で関節を選び、続く数字で角度を指定し、改行かセミコロンでコマンドを終えます。なお、日本では技適マークのない無線モジュールの使用は電波法で制限されています。

| コマンド | 意味 | 例 |
|---|---|---|
| B+角度 | ベース | B90 |
| S+角度 | 肩 | S120 |
| E+角度 | ひじ | E45 |
| W+角度 | 手首(ピッチ) | W100 |
| R+角度 | 手首の回転 | R0 |
| G+角度 | グリッパー | G110(閉じる)、G40(開く) |
| H | ホーム姿勢に戻る | H |
| K / P / X / C | 姿勢を保存/ループ再生/停止/姿勢を消去 | K |
1#include <Wire.h>
2#include <SoftwareSerial.h>
3#include <Adafruit_PWMServoDriver.h>
4
5SoftwareSerial bt(10, 11); // D10 ← HC-05 TXD, D11 → bölücü / divider → HC-05 RXD
6Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40);
7
8const byte N = 6;
9const char NAME[N] = {'B', 'S', 'E', 'W', 'R', 'G'}; // taban, omuz, dirsek, bilek, dönüş, kıskaç / base, shoulder, elbow, wrist, roll, gripper
10const byte MIN_A[N] = {0, 15, 20, 10, 0, 40}; // güvenli sınırlar / safe limits
11const byte MAX_A[N] = {180, 165, 160, 170, 180, 110};
12const byte HOME[N] = {90, 90, 90, 90, 90, 40};
13const byte MAX_POSES = 30;
14const float SPEED = 1.5; // 20 ms'de en fazla 1,5° (75°/sn) / max 1.5° per 20 ms (75°/s)
15
16float current[N];
17byte target[N];
18byte poses[MAX_POSES][N]; // kaydedilen pozlar (RAM) / recorded poses (RAM)
19byte poseCount = 0, playIndex = 0;
20bool playing = false;
21char buf[12];
22byte len = 0;
23
24void writeServo(byte ch, float a) {
25 pca.writeMicroseconds(ch, (uint16_t)(544 + a * (2400 - 544) / 180.0)); // 544–2400 µs
26}
27
28void handle(char *cmd) { // ör. / e.g. "S120", "H", "K", "P"
29 char c = toupper(cmd[0]);
30 if (c == 'H') { // başlangıç pozu / home pose
31 for (byte i = 0; i < N; i++) target[i] = HOME[i];
32 playing = false;
33 } else if (c == 'K') { // pozu kaydet / save pose
34 if (poseCount < MAX_POSES) {
35 for (byte i = 0; i < N; i++) poses[poseCount][i] = target[i];
36 poseCount++;
37 }
38 bt.print(F("Poz / pose: "));
39 bt.println(poseCount);
40 } else if (c == 'P') { // döngüyle oynat / play in a loop
41 if (poseCount > 0) { playing = true; playIndex = 0; }
42 } else if (c == 'X') { // durdur / stop
43 playing = false;
44 } else if (c == 'C') { // kayıtları sil / clear poses
45 poseCount = 0;
46 playing = false;
47 } else {
48 for (byte i = 0; i < N; i++) {
49 if (c == NAME[i]) {
50 target[i] = constrain(atoi(cmd + 1), MIN_A[i], MAX_A[i]);
51 playing = false; // elle komut oynatmayı keser / a manual command stops playback
52 }
53 }
54 }
55}
56
57bool arrived() {
58 for (byte i = 0; i < N; i++)
59 if (fabs(target[i] - current[i]) > 0.5) return false;
60 return true;
61}
62
63void setup() {
64 bt.begin(9600); // HC-05 varsayılan hızı / HC-05 default baud
65 pca.begin();
66 pca.setOscillatorFrequency(27000000);
67 pca.setPWMFreq(50);
68 for (byte i = 0; i < N; i++) {
69 target[i] = HOME[i];
70 current[i] = HOME[i];
71 writeServo(i, HOME[i]);
72 delay(300); // servolar sırayla uyansın / wake servos one by one
73 }
74}
75
76void loop() {
77 // Komutları satır satır topla / collect commands line by line
78 while (bt.available()) {
79 char ch = bt.read();
80 if (ch == '\n' || ch == '\r' || ch == ';') {
81 if (len > 0) { buf[len] = '\0'; handle(buf); len = 0; }
82 } else if (len < sizeof(buf) - 1) {
83 buf[len++] = ch;
84 }
85 }
86
87 static unsigned long last = 0, arrivedAt = 0;
88 if (millis() - last < 20) return; // 50 Hz güncelleme / 50 Hz update
89 last = millis();
90
91 if (playing) {
92 if (!arrived()) arrivedAt = millis(); // hareket sürüyor / still moving
93 else if (millis() - arrivedAt > 300) { // her pozda 300 ms bekle / hold each pose 300 ms
94 for (byte i = 0; i < N; i++) target[i] = poses[playIndex][i];
95 playIndex = (playIndex + 1) % poseCount; // sona gelince başa dön / wrap around
96 arrivedAt = millis();
97 }
98 }
99
100 for (byte i = 0; i < N; i++) {
101 float d = constrain(target[i] - current[i], -SPEED, SPEED); // hız sınırı / speed limit
102 if (d != 0) {
103 current[i] += d;
104 writeServo(i, current[i]);
105 }
106 }
107}このスケッチでは、サーボをArduinoに直接ではなくPCA9685につないでいます。これには理由があります。SoftwareSerialは1バイト受信するたびに約1 msのあいだ割り込みを止めます。サーボをServoライブラリでArduinoのピンから動かしていると、その間にパルスが伸びてしまい、コマンドのたびにアームがぴくっと動きます。PCA9685はパルスを自分で作るので、Bluetoothの通信がどれだけ忙しくても信号はまったく揺れません。記録と再生もスマートフォンから行えます。スライダーでアームをある姿勢に動かしてKを送ります。いくつか姿勢を保存したら、Pでアームがそれをループで繰り返し、Xで止まります。このバージョンでは姿勢はRAMに保存されるので、ボードの電源を切ると消えます。残しておきたい場合は、前のスケッチのEEPROMの方法を追加してください。
ESP32で作るワイヤレスロボットアーム
ESP32はクラシックBluetoothを内蔵しているので、HC-05は不要です。BluetoothSerialライブラリを使うと、ボードはスマートフォンに「BlueGrays-RobotKol」として表示され、同じコマンドを理解します。PCA9685をGPIO 21(SDA)とGPIO 22(SCL)につなぎ、VCCは3V3から給電します。このときサーボの信号は3.3 Vで出ますが、ほとんどのサーボは問題なく読み取れます。BluetoothSerialはクラシックESP32(ESP32-WROOM-32、DevKit)でしか動きません。ESP32-S2、S3、C3はクラシックBluetoothに対応していません。
1#include <Wire.h>
2#include <BluetoothSerial.h>
3#include <Adafruit_PWMServoDriver.h>
4
5BluetoothSerial bt; // klasik Bluetooth SPP / classic Bluetooth SPP
6Adafruit_PWMServoDriver pca = Adafruit_PWMServoDriver(0x40);
7
8const int N = 6;
9const char NAME[N] = {'B', 'S', 'E', 'W', 'R', 'G'}; // taban, omuz, dirsek, bilek, dönüş, kıskaç / base, shoulder, elbow, wrist, roll, gripper
10const int MIN_A[N] = {0, 15, 20, 10, 0, 40};
11const int MAX_A[N] = {180, 165, 160, 170, 180, 110};
12const int HOME[N] = {90, 90, 90, 90, 90, 40};
13float current[N], target[N];
14String line;
15
16void writeServo(int ch, float a) {
17 pca.writeMicroseconds(ch, (uint16_t)(544 + a * (2400 - 544) / 180.0)); // 544–2400 µs
18}
19
20void handle(String cmd) { // ör. / e.g. "E45", "H"
21 cmd.trim();
22 if (cmd.length() == 0) return;
23 char c = toupper(cmd[0]);
24 if (c == 'H') {
25 for (int i = 0; i < N; i++) target[i] = HOME[i];
26 return;
27 }
28 int value = cmd.substring(1).toInt();
29 for (int i = 0; i < N; i++)
30 if (c == NAME[i]) target[i] = constrain(value, MIN_A[i], MAX_A[i]);
31}
32
33void setup() {
34 Serial.begin(115200);
35 bt.begin("BlueGrays-RobotKol"); // telefonda görünen ad / name shown on the phone
36 Wire.begin(21, 22); // SDA = GPIO 21, SCL = GPIO 22
37 pca.begin();
38 pca.setOscillatorFrequency(27000000);
39 pca.setPWMFreq(50);
40 for (int i = 0; i < N; i++) {
41 current[i] = target[i] = HOME[i];
42 writeServo(i, HOME[i]);
43 delay(300);
44 }
45 Serial.println("Bluetooth hazir / ready");
46}
47
48void loop() {
49 while (bt.available()) {
50 char ch = bt.read();
51 if (ch == '\n' || ch == ';') { handle(line); line = ""; }
52 else if (line.length() < 12) line += ch;
53 }
54
55 static unsigned long last = 0;
56 if (millis() - last < 20) return; // 50 Hz güncelleme / 50 Hz update
57 last = millis();
58 for (int i = 0; i < N; i++) {
59 float d = constrain(target[i] - current[i], -1.5f, 1.5f); // yumuşak hareket / smooth motion
60 if (d != 0) {
61 current[i] += d;
62 writeServo(i, current[i]);
63 }
64 }
65}Raspberry Pi Picoで作るロボットアーム(MicroPython)
Raspberry Pi Picoでサーボを動かすのに追加のライブラリはいりません。PWMを50 Hzに設定し、duty_ns()でパルス幅をナノ秒単位で指定するだけです。PicoにはPWMチャンネルが16あるので、サーボ6個を直接動かせます。同じスライスを共有するピンのペアは周波数も共有しますが、どのサーボも50 Hzを使うので問題ありません。制約はアナログ側にあります。Picoで使えるADCピンはGP26、GP27、GP28の三つだけです。そのため下のコードでは、三つの関節をポテンショメーターで動かし、グリッパーはGP14のボタンで開閉します。Picoは3.3 Vで動くので、ポテンショメーターは3V3につなぎます。サーボはこれまでと同じく外部の5 V電源から給電します。
1from machine import Pin, PWM, ADC
2import time
3
4# Servo pinleri: taban, omuz, dirsek, kıskaç / servo pins: base, shoulder, elbow, gripper
5servos = [PWM(Pin(p)) for p in (10, 11, 12, 13)]
6for s in servos:
7 s.freq(50) # 50 Hz servo sinyali / 50 Hz servo signal
8
9pots = [ADC(26), ADC(27), ADC(28)] # Pico'da 3 ADC pini var / the Pico has 3 ADC pins
10button = Pin(14, Pin.IN, Pin.PULL_UP) # kıskaç butonu GND'ye / gripper button to GND
11
12MIN_A = (0, 15, 20, 40) # güvenli sınırlar / safe limits
13MAX_A = (180, 165, 160, 110)
14
15def write(i, angle):
16 angle = max(MIN_A[i], min(MAX_A[i], angle))
17 servos[i].duty_ns(544000 + int(angle * 10311)) # 544–2400 µs aralığı / 544–2400 µs range
18
19smooth = [p.read_u16() for p in pots]
20grip_closed = False
21last_btn = 1
22write(3, MIN_A[3]) # kıskaç açık başlar / gripper starts open
23
24while True:
25 for i in range(3):
26 smooth[i] += (pots[i].read_u16() - smooth[i]) * 0.2 # yumuşatma / smoothing
27 write(i, MIN_A[i] + (MAX_A[i] - MIN_A[i]) * smooth[i] / 65535)
28 b = button.value()
29 if b == 0 and last_btn == 1: # yeni basış / new press
30 grip_closed = not grip_closed
31 write(3, MAX_A[3] if grip_closed else MIN_A[3])
32 last_btn = b
33 time.sleep_ms(20) # 50 Hz döngü / 50 Hz loopさらに先へ:逆運動学
ここまでは各関節の角度を一つずつ指定してきました。これを順運動学と呼びます。逆運動学はその逆で、グリッパーを届かせたい点(x, y)を与えると、コードが必要な関節の角度を計算します。肩〜ひじの長さがL1、ひじ〜手首の長さがL2の2リンクアームでは、まずD = (x² + y² − L1² − L2²) / (2·L1·L2)を求めます。ひじの角度はθ2 = acos(D)、肩の角度はθ1 = atan2(y, x) + atan2(L2·sin θ2, L1 + L2·cos θ2)です(ひじを上にした解)。たとえばL1 = L2 = 12 cmで、肩の15 cm前方・5 cm上の点なら、D ≈ −0.13、θ2 ≈ 97.6°、θ1 ≈ 67.2°になります。上腕は水平から67度上を向き、前腕は上腕に対して97.6度下へ曲がります。Dの絶対値が1より大きい場合、その点はアームの届く範囲の外です。ベースの角度は、目標を上から見た位置から別に計算します。結果をサーボの角度に変換するときは、各サーボの取り付け向きと中央位置を考慮してください。
震えとリセット:よくある間違いと対策
| 症状 | 考えられる原因 | 対策 |
|---|---|---|
| アームが止まっているのにサーボが震えてジーと鳴る | ポテンショメーターの読み取り値のノイズ、または電源不足 | 平滑化フィルタと2°の不感帯を入れ、外部電源と1000 µFのコンデンサを使う |
| アームが動くとArduinoがリセットされ、HC-05の接続が切れる | サーボの起動電流で電圧が下がる | サーボを別の5〜6 V電源から給電し、GNDをつなぎ、電源投入時はサーボを一つずつ起動する |
| Bluetoothのコマンドが届くたびにサーボがぴくっと動く | SoftwareSerialの割り込みがServoライブラリのパルスを乱している | サーボをPCA9685に移すか、ESP32の内蔵Bluetoothを使う |
| 電源投入時にアームがどこかへ勢いよく動く | サーボは最後の位置を覚えておらず、最初の指令で全速力で動く | 電源を切る前にアームをホーム姿勢にしておき、attach()の前にwrite()を呼ぶ |
| 肩がアームを持ち上げられない、またはゆっくり垂れ下がる | トルク不足 | トルク計算をする。MG996R、短いアーム、カウンターウェイト、ばねを使う |
| 関節が片方の端でストールして熱くなる | サーボが機械的なストッパーを押し続けている | キャリブレーションで求めたMIN_AとMAX_Aの限界をコードに入れる |
| 物をつかんでいる間にグリッパーのサーボが熱くなる | グリッパーが閉じようとし続け、サーボがストールしたままになっている | 閉じ角度を物にちょうど触れる位置にし、指にゴムを貼る |
| PCA9685はつながっているのにサーボが一つも動かない | V+端子に電源が来ていない、またはGNDが共通になっていない | V+に5〜6 Vを供給し、I2Cアドレス(0x40)を確認する |
| 負荷がかかると関節が滑る | プラスチックのホーンの歯がつぶれた、または中央のねじがない | 金属ホーンを使い、中央のねじで軸に固定する |
ロボットアームのプロジェクトアイデア
- 色を仕分けるアーム:TCS3200カラーセンサーで立方体の色を読み取り、アームが色ごとに別の箱へ入れます。
- ジェスチャー操作:手袋にMPU6050を付けると、ロボットアームが手首と同じ向きに傾きます。
- ワイヤレスのリモコン:2本のジョイスティックを別のArduinoにつなぎ、nRF24L01でコマンドを送れば、スマートフォンのいらないリモコンになります。
- ブラウザから操作:ESP32でWebページにスライダーを作り、同じWi-Fiネットワーク上のどの端末からでもアームを操作します。ESP32のWebSocketを使えば遅延を最小限にできます。
- コンベアからピッキング:ステッピングモーターで小さなベルトコンベアを作り、TCRT5000が物を見つけたら、アームがベルトから拾い上げます。
- コンテストや学校のプロジェクト:記録と再生ができるアームは、機構、電子回路、ソフトウェアをまとめて見せられるので、Arduinoの科学コンテスト向けプロジェクトアイデアの中でも有力な選択肢です。
Arduinoロボットアームは、機構設計、トルク計算、電源管理、なめらかな動きのプログラミングを一度に学べるプロジェクトで、手をかけるほど大きく育ちます。まずはポテンショメーターで操作するサーボ4個のアームから始め、記録と再生を加え、それからPCA9685とBluetoothでサーボ6個へステップアップしましょう。BlueGraysアプリには、ポテンショメーターやジョイスティックでのサーボ操作、スマートフォンからのBluetooth操作、サーボ32個の振り付けといったプロジェクトが、回路図とステップごとのコード付きでそろっています。アームの関節を一つずつ先に試してみることができます。
よくある質問
Arduinoロボットアームにはどのサーボモーターを使えばいいですか?
ベース、肩、ひじのように負荷のかかる関節には、9〜11 kg·cmのトルクを持つ金属ギアのMG996Rがおすすめです。手首やグリッパーのような軽い関節には、MG90SかSG90で十分です。SG90だけで作る小型アームも短く軽くすれば動きますが、持ち上げられるのは数十グラムの物だけです。
ロボットアームにはどんな電源が必要ですか?
SG90またはMG90Sを4個使う小型アームなら、5 V 3 AのACアダプターで足ります。MG996R 3個と小型サーボ3個のサーボ6個のアームには、5 A以上、余裕を持たせるなら10 Aの5〜6 VスイッチングACアダプターを使ってください。ACアダプターのマイナスをArduinoのGNDにつなぐのを忘れないでください。
Arduinoで何個のサーボを制御できますか?
Servoライブラリは、Arduino Unoで最大12個、Megaで48個のサーボを動かせます。ただし本当の限界は電流で、サーボはすべて外部電源から給電する必要があります。PCA9685モジュールならI2Cのピン2本だけでサーボ16個を動かせ、最大62枚までつなげられます。
ロボットアームのサーボが震えるのはなぜですか?
よくある原因は、電源不足、共通GNDの未接続、ポテンショメーターの読み取り値のノイズです。外部電源を使ってサーボの電源ラインに1000 µFのコンデンサを入れ、コードには平滑化フィルタと不感帯を加えてください。SoftwareSerialとServoライブラリを一緒に使っている場合は、サーボをPCA9685に移すと震えが完全になくなります。
SG90のロボットアームはどれくらいの重さを持ち上げられますか?
SG90のストールトルクは約1.8 kg·cmで、安全に動かすにはその半分の0.9 kg·cmを目安にします。アームとサーボ自身の重さでその一部を使ってしまうため、長さ15 cmほどのSG90のアームが実際に運べるのは、ペットボトルのキャップや小さなスポンジの立方体のような20〜30 gの軽い物だけです。
スマートフォンからロボットアームを操作するにはどうすればいいですか?
ArduinoにHC-05 Bluetoothモジュールをつなぎ、スマートフォンのBluetoothシリアルターミナルかスライダー付きのコントローラーアプリで「S120」のような文字のコマンドを送ります。ESP32ならBluetoothを内蔵しているので追加のモジュールは不要です。HC-05はiPhoneでは動かないので、iPhoneにはBLEモジュールかESP32 BLEが必要です。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
ポテンショメーターでサーボモーターを操作
ポテンショメーターのつまみを回すと、サーボモーターのアームもそれに合わせて回ります。つまみの一方の端が0度、もう一方の端が半回転に対応します。Arduinoがつまみの位置を常に読み取り、サーボモーターが動きにすぐ反応します。ロボットアームや回転式カメラ台などのプロジェクトへの第一歩です。

ジョイスティックでサーボモーターを制御
防犯カメラの向きを変えるように、ジョイスティックで2つのモーターを操作します。スティックを左右に倒すと下側のサーボモーターがゆっくり回り、上下に倒すと上側のサーボモーターが上下に傾きます。手を離すと、どちらもその位置で止まります。カメラやレーザーの台座など、2方向に動く装置の基礎を作ります。

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

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

BluetoothでスマートフォンからLEDを操作
スマートフォンで部屋の照明をつけたり消したりするための、小さな練習です。Bluetoothモジュールがスマートフォンから送られた文字をArduinoに伝えます。ある文字で赤いLEDが、別の文字で緑のLEDが点灯し、同じ文字を小文字で送ると消灯します。無線操作の仕組みを、ごくシンプルな形で学べます。

L298Nで作るBluetooth操作の車
スマートフォンをリモコンにして、自作のロボットカーを操作できます。ArduinoはBluetoothモジュールを通じてスマートフォンのリモコンアプリから指令を受け取り、モータードライバーで車輪を回します。前進、後退、左右や斜めへの移動ができ、速度もスマートフォンから調整できます。停止ボタンを押すと、短いブレーキ動作でモーターがすぐに止まります。

サーボの円形ダンス — 32個のサーボで動く模様
Arduino Megaで、円形に並べた32個のサーボモーターを動かすビジュアルアニメーションのプロジェクトです。サーボは1つの配列で管理し、Megaの22–53番ピンに接続します。ランダムな角度でばらばらに振動する動き、全体が同時に動く同期スイープ、円周を滑らかに巡る波、左右に揺れる方位磁針の針という4種類の動きを見せます。回転する波の効果は、各サーボと動作中のサーボとの関係によって生まれます。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

