生成AIでロボットアームを動かす方法
最近、ChatGPT や生成AIが話題ですが、そこからさらに生成AIを利用して、ロボットを動かすフィジカルAIというのが注目されています。
今回は、フィジカルAIと言って良いのかわかりませんが、音声でAIに命令を理解させ、実際にロボットアームを動かす方法を紹介します。
完成品は動画のようなことができます。
音声認識やArduino制御、ローカルLLMも組み合わせることで、AIと会話しながらロボットを操作できる環境を構築しています。
ロボットアームの作り方について
ロボットアームの作り方は下の記事で解説しています。
是非読んでみてください。

使用するAIについて
今回使用するAIは、人間とテキストベースで会話する言語モデル:LLM(large language model)、音声をテキストに変換するSTTモデル(speech to text)、テキストを音声に変換するTTSモデル(text to speech)の3つを利用しています。
①言語モデル(LLM):ChatGPT APIまたはローカルLLM(gemma 2)
②音声➡︎テキストモデル(STT):faster-whisper
③テキスト➡︎音声モデル(TTS):VOICEVOX
注意点
はじめに、お断りしておかないといけないのですが、上の動画を撮影した2023年時点では、STTモデル、TTSモデルともにCPU版を利用していたため、応答にかなり時間がかかっています。
そのため、動画内では、返答までの時間を編集でカットしています。
今回の2026年版では、応答速度を上げるため、STTモデル、TTSモデルともにGPU版を利用しました。
その際の実際の応答速度は下の動画となります。
会話用のAI(LLM)について
会話用のAI(LLM)は、ChatGPTのAPIを利用する方法と自分のパソコンでローカルLLMを利用する場合の二つのパターンを用意しました。
それぞれのメリットとデメリットは以下の通りです。
ChatGPT API
ChatGPTなので、かなり高性能です。
適当な指示でも汲み取ってくれる可能性が高いです。
ただし、APIの契約が必要で、使用量に応じて、利用料金が発生します。
ローカルLLM
こちらは、自宅のPCで生成AIを実行するタイプです。
ChatGPTに比べると流石に性能は落ちますが、今回の用途であれば、性能的には十分です。
上記動画はこちらのローカルLLMで実行しています。
メリットは自宅PCで実行できるので、無料で動かせることです。
ただし、実行にはGPUが必要ですので、ご注意ください。
今回はGoogleから公開されているオープンソースのAIモデル:gemma 2-9B-itを利用していますが、GPUのスペック次第でより高性能なモデルも利用できます。
ローカルLLMの具体的な設定方法は、下の記事で説明していますので、是非見てください。

VOICEVOX
音声生成には、VOICEVOXを使用(VOICEVOX:ずんだもん)しています。
キャラクターごとに利用規約があるため、使用時はご注意ください。
ソフトウェア自体は無料で以下サイトからダウンロードできます。
ありがたいことに、ずんだもんはクレジット表記をすれば、無料で使用可能です。

私はWindowsのGPUを使用しており、CUDAも使用しているため、
「Windows(GPU/CUDA版) part 1 part 2」をダウンロードしました。
Part 1、Part 2をそれぞれダウンロードし、同じフォルダでPart 1を展開するとまとめて展開されます。
展開されたフォルダの中にrun.exeがあるので、ダブルクリックすると、VOICEVOXの APIが起動します。
コードについて
プログラムの構成
全体の処理の流れとしては、以下のようになっています。
- マイクから音声を入力
- faster-whisperで音声をテキスト化(STT)
- LLM(ChatGPT API または ローカルLLM)が命令内容を解析
- VOICEVOXでAIの返答音声を生成・再生(TTS)
- Arduinoへシリアル通信でコマンドを送信
- ロボットアームが実際に動作
こちらがプログラムの全体構成です。
├─ app.py
│ └─ Pythonの本体コード(全体の制御)
│ ・音声認識、LLM処理、音声合成の各モジュールを呼び出し・連携
│ ・生成された音声ファイルの再生
│ ・Arduinoへコマンド(数値など)を送信
│
├─ stt_engine.py
│ └─ 音声認識用
│ ・マイク録音の制御
│ ・faster-whisper による音声認識(テキスト化)
│
├─ tts_engine.py
│ └─ テキストtoスピーチ用
│ ・音声合成モデルを扱う処理(テキストから音声ファイルを生成)
│
├─ llm_backend.py
│ └─ LLM切替・応答処理用
│ ・config.pyの設定に応じて ChatGPT API または ローカルLLM を呼び出し
│ ・LLMの応答テキストから命令(コマンド)を抽出・変換
│
├─ config.py
│ └─ 設定まとめ
│ ・.envから環境変数を読み込む
│ ・LLM種別(api / local)、APIキー、ローカルLLMのURLやモデル名を提供
│
│
├─ .env
│ └─ 秘密情報・環境変数
│ ・LLM_MODE(APIかローカルかの切り替え)
│ ・OPENAI_API_KEY、OPENAI_MODEL
│ ・LOCAL_LLM_URL、LOCAL_LLM_MODEL
│
│
│
│ ##### ここから別環境 #####
├─ llm_server.py
│ └─ ローカルLLM実行用(PC内サーバー)
│
│
└─ Arduino/
└─ LLM_robot_arm_arduino.ino
└─ ロボットアームを制御するArduinoコード
ライブラリのインストール
ロボットアーム用のコードとLLM用のコードを用意しており、私は仮想環境を分けています。
その方が、ライブラリが干渉するリスクがないので、安全です。
◼️ロボットアーム用のライブラリ
# 1. 設定管理・通信用(APIキー等の読み込みやLLMとの通信)
pip install python-dotenv openai requests
# 2. 音声認識用(マイク録音とfaster-whisperによる文字起こし)
pip install faster-whisper sounddevice soundfile numpy
# 3. ロボットアーム制御用(Arduinoとのシリアル通信)
pip install pyserial◼️ローカルLLM用のライブラリ
ローカルLLMに必要なライブラリは下の記事で説明していますが、追加で、以下のライブラリも必要です。
#FastAPI本体と、サーバー起動用のuvicornをインストールします
pip install fastapi uvicorn
プログラム実行手順
プログラムの実行は、下の順番で実行します。
1から3はどの順番でも問題なく、4を最後に実行して貰えば、OKです。
- ローカルLLM用APIの「llm_server.py」
- VOICEVOXの「run.exe」
- ロボットアーム用の「LLM_robot_arm_arduino.ino」
- 統括用の「app.py」
終了時は、音声操作で「EXIT(イグジット)」というと終了します。
コード
最後にコードを紹介します。
◼️ 編集箇所①
「config.py」は各種モデル用の設定ファイルです。
そのままでも大丈夫ですが、必要に応じて編集してください。
◼️ 編集箇所②
「.env」はChatGPT APIのAPIを記載したり、APIとローカルLLMを切り替えたりすると用の設定ファイルです。
お使いの環境に合わせて編集してご利用ください。
◼️ 編集箇所③
llm_server.pyはローカルLLM用のコードです。
ローカルLLMを利用される場合は、モデルのパスを編集ください。
app.py
# app.py
from llm_backend import ask_llm
from tts_engine import generate_voice
from stt_engine import listen_and_transcribe
import winsound
import time
import serial
# ===== シリアル通信設定 =====
# configからシリアル設定をインポート
from config import SERIAL_PORT, SERIAL_BAUDRATE
EXIT_PHRASE = "EXIT"
# configの値を使用する
ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE)
def robot_arm(val):
"""
過去コードと同じ方式:
1文字のコマンドを100回送信する
"""
val = str(val)
for i in range(100):
ser.write(bytes(val, "utf-8"))
time.sleep(0.01)
def main():
print("常時監視版 音声対話")
print("終了するには exit または 終了 と話してください")
# ===== シリアル通信用初期動作 =====
robot_arm("1")
time.sleep(1) # Arduino側との接続待ち
while True:
text = listen_and_transcribe()
if not text:
continue
text = text.strip()
print("あなた:", text)
# ユーザー発話で終了
if text.lower() == "exit" or "終了" in text or "ストップ" in text:
print("終了します")
break
# ===== LLM =====
result = ask_llm(text)
reply = result.get("reply", "")
commands = result.get("commands", [])
print("AI:", reply)
print("commands:", commands)
# AI側から終了指示
if reply == EXIT_PHRASE:
print("終了します")
break
# ===== 音声出力 =====
if reply:
wav = generate_voice(reply, "output.wav")
winsound.PlaySound(str(wav), winsound.SND_FILENAME)
time.sleep(1.0)
# ===== コマンド処理 =====
if commands:
for cmd in commands:
print("実行コマンド:", cmd)
robot_arm(cmd)
time.sleep(1)
ser.close()
if __name__ == "__main__":
main()stt_engine.py
# stt_engine.py
from faster_whisper import WhisperModel
import sounddevice as sd
import soundfile as sf
import numpy as np
# ===== STT設定 =====
from config import (
WHISPER_MODEL,
WHISPER_DEVICE,
WHISPER_COMPUTE_TYPE,
WHISPER_SAMPLE_RATE as SAMPLE_RATE
)
model = None
def load_model():
global model
if model is None:
model = WhisperModel(
WHISPER_MODEL, # configの値を使用
device=WHISPER_DEVICE, # configの値を使用
compute_type=WHISPER_COMPUTE_TYPE # configの値を使用
)
return model
# -------- 固定録音 --------
def record_and_transcribe():
seconds = 5
audio = sd.rec(
int(seconds * SAMPLE_RATE),
samplerate=SAMPLE_RATE,
channels=1,
dtype="float32"
)
sd.wait()
sf.write("input.wav", audio, SAMPLE_RATE)
return transcribe("input.wav")
# -------- 常時監視・連続録音版 --------
def listen_and_transcribe():
block = 0.2
threshold = 0.01
silence_limit = 25
block_size = int(block * SAMPLE_RATE)
frames = []
silence = 0
recording = False
print("音声待機中...")
with sd.InputStream(
samplerate=SAMPLE_RATE,
channels=1,
dtype="float32",
blocksize=block_size
) as stream:
while True:
data, overflowed = stream.read(block_size)
if overflowed:
print("警告: 音声入力が一部欠落しました")
vol = float(np.sqrt((data ** 2).mean()))
# 発話開始待ち
if not recording:
if vol > threshold:
print("録音開始")
recording = True
frames.append(data.copy())
continue
# 録音中
frames.append(data.copy())
if vol < threshold:
silence += 1
else:
silence = 0
# 無音が続いたら録音終了
if silence > silence_limit:
print("録音終了")
break
if not frames:
return ""
audio = np.concatenate(frames)
sf.write("input.wav", audio, SAMPLE_RATE)
return transcribe("input.wav")
# -------- 文字起こし --------
def transcribe(path):
m = load_model()
segments, _ = m.transcribe(
path,
language="ja",
beam_size=5,
best_of=5,
temperature=0.0,
vad_filter=True,
condition_on_previous_text=False,
)
text = "".join([s.text for s in segments]).strip()
return texttts_engine.py
# tts_engine.py
from pathlib import Path
import requests
# config.py から設定値をインポートする
from config import VOICEVOX_URL, VOICEVOX_SPEAKER_ID as SPEAKER_ID
def generate_voice(text, output_path="output.wav"):
"""
VOICEVOX APIを使ってtextを音声化し、wavファイルに保存する。
app.py側の呼び出し仕様に合わせて Path を返す。
"""
if not text or not text.strip():
return None
output_path = Path(output_path)
# ① 音声クエリ作成
query_response = requests.post(
f"{VOICEVOX_URL}/audio_query",
params={
"text": text,
"speaker": SPEAKER_ID,
},
timeout=30,
)
query_response.raise_for_status()
# ② 音声合成
synthesis_response = requests.post(
f"{VOICEVOX_URL}/synthesis",
params={
"speaker": SPEAKER_ID,
},
json=query_response.json(),
timeout=60,
)
synthesis_response.raise_for_status()
# ③ wav保存
output_path.write_bytes(synthesis_response.content)
return output_pathllm_backend.py
# llm_backend.py
import requests
import re
from config import *
SYSTEM_PROMPT = """
あなたはロボットアーム操作にも対応できる音声アシスタントです。
通常の会話には、1行または2行で自然に簡潔に返答してください。
ユーザーがロボットアームの操作を明確に指示した場合は、必ず対応する数字を半角ダブルクオーテーションで囲って返してください。
右に動かす場合は"2"、左に動かす場合は"0"、上に動かす場合は"3"、下に動かす場合は"4"、手前に動かす場合は"5"、奥に動かす場合は"6"、グリップを開く場合は"7"、グリップを閉じる場合は"8"。
返答例は以下とおりです。
右に動かして → 右に動かします。"2"
右に動かした後、左に動かして → 右に動かします。"2"、左に動かします。"0"
グリップを開いて → グリップを開きます。"7"
終了・ストップ・会話終了の意味なら EXIT のみ返してください。
"""
conversation_history = [
{"role": "system", "content": SYSTEM_PROMPT}
]
MAX_HISTORY = 10
def ask_llm(text):
global conversation_history
conversation_history.append({
"role": "user",
"content": text
})
if LLM_MODE == "api":
from openai import OpenAI
client = OpenAI(api_key=OPENAI_API_KEY)
res = client.chat.completions.create(
model=OPENAI_MODEL,
messages=conversation_history
)
reply = res.choices[0].message.content
else:
res = requests.post(
LOCAL_LLM_URL,
json={
"messages": conversation_history,
"max_tokens": 512,
"temperature": 0.6
},
timeout=60
)
reply = res.json().get("response", "")
conversation_history.append({
"role": "assistant",
"content": reply
})
if len(conversation_history) > MAX_HISTORY:
conversation_history = (
[conversation_history[0]] +
conversation_history[-(MAX_HISTORY - 1):]
)
# commands = re.findall(r'"([0-8])"', reply)
commands = re.findall(r'"([02345678])"', reply)
return {
"reply": reply,
"commands": commands
}config.py
# config.py
import os
from pathlib import Path
from dotenv import load_dotenv
# .envファイルから環境変数を読み込む
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent
# ==========================================
# 1. LLM (推論エンジン) の設定
# ==========================================
# "api" にするとChatGPT、"local" にするとローカルLLMを使用
LLM_MODE = os.getenv("LLM_MODE", "local")
# APIモード用 (ChatGPT)
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
# ローカルモード用 (FastAPI等で構築したGemma等のサーバー)
LOCAL_LLM_URL = os.getenv("LOCAL_LLM_URL", "http://127.0.0.1:8100/chat")
LOCAL_LLM_MODEL = os.getenv("LOCAL_LLM_MODEL", "gemma")
# ==========================================
# 2. 音声認識 (STT: faster-whisper) の設定
# ==========================================
WHISPER_MODEL = "small"
# GPUが使える場合は "cuda"、使えない場合は "cpu" に変更
WHISPER_DEVICE = "cuda"
# GPUなら "float16"、CPUなら "int8" を推奨
WHISPER_COMPUTE_TYPE = "float16"
WHISPER_SAMPLE_RATE = 16000
# ==========================================
# 3. 音声合成 (TTS: VOICEVOX) の設定
# ==========================================
VOICEVOX_URL = "http://127.0.0.1:50021"
VOICEVOX_SPEAKER_ID = 1 # ずんだもん等、好きな話者IDに変更
# ==========================================
# 4. ロボットアーム (シリアル通信) の設定
# ==========================================
# Windowsなら "COM3" 等、Mac/Linuxなら "/dev/ttyUSB0" 等に変更
SERIAL_PORT = "COM3"
SERIAL_BAUDRATE = 115200.env
# ===== モード切り替え =====
# LLM_MODE=api
LLM_MODE=local
# ===== OpenAI =====
OPENAI_API_KEY=sk-proj-************************************************
OPENAI_MODEL=gpt-4o-mini
# ===== ローカルLLM =====
LOCAL_LLM_URL=http://127.0.0.1:8100/chat
LOCAL_LLM_MODEL=gemmallm_server.py
# llm_server.py
# -*- coding: utf-8 -*-
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from llama_cpp import Llama
import uvicorn
# ===== 設定 =====
MODEL_PATH = r"【モデルを保存している場所のパスを記載】\gemma-2-9B-it-SimPO-Q5_K_M.gguf"
HOST = "127.0.0.1"
PORT = 8100
# ===== FastAPI =====
app = FastAPI()
print("[INFO] Loading LLM model...")
llm = Llama(
model_path=MODEL_PATH,
n_ctx=8000,
n_gpu_layers=-1,
n_batch=512,
verbose=True,
seed=0,
)
print("[INFO] LLM model loaded.")
# ===== リクエストモデル =====
class ChatRequest(BaseModel):
messages: list
max_tokens: int = 512
temperature: float = 0.6
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/chat")
def chat(req: ChatRequest):
"""
OpenAI API風の messages 形式でローカルLLMに問い合わせるAPI。
"""
try:
if not req.messages:
raise HTTPException(status_code=400, detail="messages is empty")
output = llm.create_chat_completion(
messages=req.messages,
max_tokens=req.max_tokens,
temperature=req.temperature,
)
text = output["choices"][0]["message"]["content"]
return {
"response": text
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"LLM generation failed: {str(e)}"
)
if __name__ == "__main__":
uvicorn.run(app, host=HOST, port=PORT)
LLM_robot_arm_arduino.ino
//-----サーボ制御用設定
#include <PCA9685.h> //PCA9685用ヘッダーファイル
PCA9685 pwm = PCA9685(0x40); //PCA9685のアドレス指定
//-----各変数の設定
#define SERVOMIN 150 //最小パルス幅
#define SERVOMAX 500 //最大パルス幅
#define servo_01 0 //サーボ1のピンの設定
#define servo_02 1 //サーボ2のピンの設定
#define servo_03 2 //サーボ3のピンの設定
#define servo_04 3 //サーボ4のピンの設定
#define servo_05 4 //サーボ5のピンの設定
#define servo_06 5 //サーボ6のピンの設定
double ini_arg_01 = 90; // サーボの初期角度
double ini_arg_02 = 180; // サーボの初期角度
double ini_arg_03 = 135; // サーボの初期角度
double ini_arg_04 = 135; // サーボの初期角度
double ini_arg_05 = 90; // サーボの初期角度
double ini_arg_06 = 90; // サーボの初期角度 ※シリアル通信版のinitial_positionに合わせる
double arg_01 = ini_arg_01; // サーボの角度
double arg_02 = ini_arg_02; // サーボの角度
double arg_03 = ini_arg_03; // サーボの角度
double arg_04 = ini_arg_04; // サーボの角度
double arg_05 = ini_arg_05; // サーボの角度
double arg_06 = ini_arg_06; // サーボの角度
double ARM_LEN2 = 105; // アーム2の長さ105mm
double ARM_LEN3 = 100; // アーム3の長さ100mm
//次の円筒座標位置は物理的に可能な位置か判定する 5mmの裕度を与える
double chiiki_MAX = pow((ARM_LEN2 - 5) + (ARM_LEN3 - 5), 2.0);
double chiiki_MIN = pow(ARM_LEN2 - ARM_LEN3, 2.0);
double r_now = 0; // 円筒座標系の位置 r
double fai_now = 0; // 円筒座標系の位置 fai
double pz_now = 0; // 円筒座標系の位置 pz
int X_POS_L = 128; // LスティックのX軸方向の読み取り値
int Y_POS_L = 128; // LスティックのY軸方向の読み取り値
int X_POS_R = 128; // RスティックのX軸方向の読み取り値
int Y_POS_R = 128; // RスティックのY軸方向の読み取り値
int GRIP_servo5 = 128; // サーボ5の入力(L1,R1)
int GRIP_L2 = 128; // アームのグリップ部 閉まる方向
int GRIP_R2 = 128; // アームのグリップ部 開く方向
int read_i = 0;
int count_i = 0;
int out_i = 0;
int arg_x = 0;
int serial_data = -1;
//-----サーボ制御end
//-----初期設定
void setup() {
pwm.begin(); //初期設定 (アドレス0x40用)
pwm.setPWMFreq(50); //PWM周期を50Hzに設定 (アドレス0x40用)
initial_position(arg_01, arg_02, arg_03, arg_04, arg_05, arg_06); //ポジション初期化
//pythonとシリアル通信
Serial.begin(115200);
}
//-----メインループ
void loop() {
//-----ロボットアームを動かす関数
if (Serial.available() > 0) {
serial_data = Serial.read();
switch (serial_data) {
case '0':
X_POS_L = 50;
break;
case '2':
X_POS_L = 200;
break;
case '3':
Y_POS_R = 50;
break;
case '4':
Y_POS_R = 200;
break;
case '5':
Y_POS_L = 50;
break;
case '6':
Y_POS_L = 200;
break;
case '7':
arg_06 = 45; // 最大まで開く
break;
case '8':
arg_06 = 90; // 初期位置へ戻す・閉じる
break;
default:
break;
}
}
//-----ロボットアームを動かす関数end
//円筒座標系の位置計算
calculate_rfaipz(&r_now, &fai_now, &pz_now, arg_01, arg_02, arg_03);
//円筒座標系の移動量Y_POS_L、Y_POS_Rからarg_02、arg_03を計算
inverse_kinematics(Y_POS_L, Y_POS_R, r_now, pz_now, arg_02, arg_03, &arg_02, &arg_03);
//-----サーボモーター用にコントローラー入力→角度_変換
arg_01 = arg_01 + convert_01(X_POS_L, arg_01);
//arg_02 = arg_02 + convert_02(Y_POS_L, arg_02);
//arg_03 = arg_03 + convert_03(Y_POS_R, arg_03);
arg_04 = arg_04 + convert_03(X_POS_R, arg_04);
arg_05 = arg_05 + 20 * convert_01(GRIP_servo5, arg_05);
// arg_06 = arg_06 + convert_04(GRIP_L2, GRIP_R2, arg_06);
double r_pre = r_now;
double pz_pre = pz_now;
//円筒座標系の位置計算
calculate_rfaipz(&r_now, &fai_now, &pz_now, arg_01, arg_02, arg_03);
//-----サーボ制御(関数呼び出し)
servo_write(servo_01, arg_01);
servo_write(servo_02, arg_02);
servo_write(servo_03, arg_03);
servo_write(servo_04, arg_04);
servo_write(servo_05, arg_05);
servo_write(servo_06, arg_06);
//-----入力リセット
X_POS_L = 128; // LスティックのX軸方向の読み取り値
Y_POS_L = 128; // LスティックのY軸方向の読み取り値
X_POS_R = 128; // RスティックのX軸方向の読み取り値
Y_POS_R = 128; // RスティックのY軸方向の読み取り値
GRIP_L2 = 128; // アームのグリップ部 閉まる方向
GRIP_R2 = 128; // アームのグリップ部 開く方向
GRIP_servo5 = 128; // サーボ5
}
//-----メインループend
呼び出し関数部分です。
私は関数を別ファイルに作成していますが、同一ファイル内に作成しても問題ありません。
/////-----呼び出し用の関数まとめ-----/////
//-----サーボ出力用_サーボ番号、角度→出力_関数
void servo_write(int ch, double ang){ //動かすサーボチャンネルと角度を指定
ang = map(ang, 0, 180, SERVOMIN, SERVOMAX); //角度(0~180)をPWMのパルス幅(150~500)に変換
pwm.setPWM(ch, 0, ang);
}
//-----サーボ位置初期化用関数
void initial_position(double arg_01_x, double arg_02_x, double arg_03_x, double arg_04_x, double arg_05_x, double arg_06_x){
//各サーボの初期値
arg_01 = ini_arg_01; // サーボの角度
arg_02 = ini_arg_02; // サーボの角度
arg_03 = ini_arg_03; // サーボの角度
arg_04 = ini_arg_04; // サーボの角度
arg_05 = ini_arg_05; // サーボの角度
arg_06 = ini_arg_06; // サーボの角度
//サーボ出力
servo_write(servo_01, arg_01);
delay(500);
servo_write(servo_02, arg_02);
delay(500);
servo_write(servo_03, arg_03);
delay(500);
servo_write(servo_04, arg_04);
delay(500);
servo_write(servo_05, arg_05);
delay(500);
servo_write(servo_06, arg_06);
delay(500);
}
//-----リセット用関数
void software_reset() {
asm volatile (" jmp 0");
}
//-----サーボ1、サーボ5用_コントローラー入力→角度_変換関数
int convert_01(int input_x, double arg_x){
if(arg_x == 170 && input_x > 190){ //サーボ角度制御、170度以下
return 0;
}
else if (arg_x == 10 && input_x < 65){ //サーボ角度制御、10度以上
return 0;
}
else if(input_x > 190){ //2度ずつ増加
return 1;
}
else if(input_x < 65){ //2度ずつ減少
return -1;
}
else{
return 0;
}
}
//-----サーボ2用_コントローラー入力→角度_変換関数
int convert_02(int input_x, double arg_x){
if(arg_x == 180 && input_x > 190){ //サーボ角度制御、180度以下
return 0;
}
else if (arg_x == 45 && input_x < 65){ //サーボ角度制御、45度以上
return 0;
}
else if(input_x > 190){ //1度ずつ増加
return 1;
}
else if(input_x < 65){ //1度ずつ減少
return -1;
}
else{
return 0;
}
}
//-----(サーボ3×)、サーボ4〇用_コントローラー入力→角度_変換関数
int convert_03(int input_x, double arg_x){
if(arg_x == 135 && input_x < 65){ //サーボ角度制御、135度以下
return 0;
}
else if (arg_x == 0 && input_x > 190){ //サーボ角度制御、0度以上
return 0;
}
else if(input_x > 190){ //1度ずつ減少
return -1;
}
else if(input_x < 65){ //1度ずつ増加
return 1;
}
else{
return 0;
}
}
//円筒座標系の座標位置を計算する関数
void calculate_rfaipz(double *r_now, double *fai_now, double *pz_now, double arg_x1, double arg_x2, double arg_x3){
double r_x = 0;
double fai_x = 0;
double pz_x = 0;
//サーボの角度を 度→ラジアンに変換
double rad_arg_x1 = radians(arg_x1);
double rad_arg_x2 = radians(arg_x2);
double rad_arg_x3 = radians(arg_x3);
r_x = ARM_LEN2 * cos(rad_arg_x2) + ARM_LEN3 * cos(rad_arg_x2 - rad_arg_x3);
fai_x = arg_x1;
pz_x = ARM_LEN2 * sin(rad_arg_x2) + ARM_LEN3 * sin(rad_arg_x2 - rad_arg_x3);
*r_now = r_x;
*fai_now = fai_x;
*pz_now = pz_x;
}
//-----Y_POS_L、Y_POS_R用_コントローラー入力→円筒座標系のr軸に変換関数
int convert_02r(int input_x){
if(input_x > 190){ //1mmずつ増加
return -1;
}
else if(input_x < 65){ //1mmずつ減少
return 1;
}
else{
return 0;
}
}
//円筒座標系の逆運動学
void inverse_kinematics(double r_now_dx, double pz_now_dx, double r_now_x, double pz_now_x, double arg_02_x, double arg_03_x, double *arg_02, double *arg_03){
//次の円筒座標位置からサーボモーター02,03の角度を求める
double arg_02_next = arg_02_x;
double arg_03_next = arg_03_x;
//現時点での座標位置を保存
double r_now_pre = r_now_x;
double pz_now_pre = pz_now_x;
//現在の円筒座標位置とコントローラーの入力から次の円筒座標位置を求める
r_now_x = r_now_x + convert_02r(r_now_dx);
pz_now_x = pz_now_x + convert_02r(pz_now_dx);
//値域が制御範囲内か確認
double chiiki_now = pow(r_now_x, 2.0) + pow(pz_now_x, 2.0);
//制御範囲内でなければ移動しない
if(chiiki_now > chiiki_MAX){
r_now_x = r_now_pre;
pz_now_x = pz_now_pre;
arg_02_next = arg_02_x;
arg_03_next = arg_03_x;
}
else if(chiiki_now < chiiki_MIN) {
r_now_x = r_now_pre;
pz_now_x = pz_now_pre;
arg_02_next = arg_02_x;
arg_03_next = arg_03_x;
}
else{
//逆運動学の公式より
arg_03_next = abs(acos((pow(r_now_x, 2.0) + pow(pz_now_x, 2.0) - (pow(ARM_LEN2, 2.0) + pow(ARM_LEN3, 2.0))) / (2 * ARM_LEN2 * ARM_LEN3)));
double S3 = sin(arg_03_next);
double C3 = cos(arg_03_next);
arg_02_next = atan2(
(ARM_LEN3 * S3 * r_now_x + (ARM_LEN2 + ARM_LEN3 * C3) * pz_now_x),
((ARM_LEN2 + ARM_LEN3 * C3) * r_now_x - ARM_LEN3 * S3 * pz_now_x)
);
//ラジアン→度に変換
arg_02_next = degrees(arg_02_next);
arg_03_next = degrees(arg_03_next);
}
//サーボ02用の角度 制御範囲内か確認
if(arg_02_next > 180){
*arg_02 = arg_02_x;
}
else if(arg_02_next < 0){
*arg_02 = arg_02_x;
}
else{
*arg_02 = arg_02_next;
}
//サーボ03用の角度 制御範囲内か確認
if(arg_03_next > 135){
*arg_03 = arg_03_x;
}
else if(arg_03_next < 0){
*arg_03 = arg_03_x;
}
else{
*arg_03 = arg_03_next;
}
}
ロボットアームの作り方について
ロボットアームの作り方は下の記事で解説しています。
是非読んでみてください。

コメント