はじめ
ヘキサポッドとか言うカッコよくて安い1の発見したのでポチった!
届いた
これ、骨組みしかなくね?2
結局使った部品
名称 | 個数 | 購入サイト | 補足 |
---|---|---|---|
ヘキサポッド/ 6本足ロボット スパイダー ロボットフルセット ブラケット アクセサリー ブラック | 1 | https://www.amazon.co.jp/dp/B07QFDPJM5 | 骨組み、ネジ+ナット、ケーブルスパイラルチューブのセット |
ネジ+ナット+平ワッシャー+スプリングワッシャー(M2) | * | https://www.amazon.co.jp/dp/B01NCRQHRK | 上記セットにM2ネジ+ナットが含まれていなかったため |
obniz | 1 | https://www.amazon.co.jp/dp/B07DD6FK8G | 心臓部 |
サーボモータードライバー PCA9685 | 1 | https://www.amazon.co.jp/dp/B07SLRG5J1 | 1つで16個のサーボモーターを制御可能 |
サーボモーター SG90 | 12 | https://www.amazon.co.jp/dp/B00VUJYNWG | 本来6本脚のロボットだが、一身上の都合3により4本脚に |
カメラ Arducam Mini 2MP | 1 | https://www.switch-science.com/catalog/3780/ | カメラが付いてた方がカッコいいと思った |
モバイルバッテリー+USBケーブル(A-MicroB) | 1 | https://store.shopping.yahoo.co.jp/meiseishop/539a1.html | 出力2.1Aの割りに軽かったので採用、電流低いとサーボが上手く動かない… |
ブレッドボード | 1 | http://akizukidenshi.com/catalog/g/gP-05155/ | 小さめのものを採用 |
L型ヘッダーピン | 1 | http://akizukidenshi.com/catalog/g/gC-05336/ | obnizのJ1ピンに半田付けしておくと電源の取り回しが楽になる。新型では不要 |
両側ロングヘッダーピン | 1 | http://akizukidenshi.com/catalog/g/gC-09056/ | obnizをブレッドボードに接続する時に使用 |
ジャンパーワイヤー | * | 短い物もあると良い | |
スポンジ両面テープ | * | 百均ので十分 | |
ケーブルタイ | * | 百均ので十分 |
obniz とは
公式サイト
端的に言うとJavascriptで電子工作できヤツ
今回使う部品のライブラリもあるので、驚くほど簡単に作れるハズ
サーボモータードライバー とは
obniz単体でもサーボモーターの制御は可能だが、流石に12個も制御できない4ので採用
大体の手順
部品の動作確認
パーツライブラリのサーボドライバーやサーボを参考に動作確認を行う
この時、サーボを90°に設定してサーボホーンを付けておくと組み立て時に二度手間にならない…
カメラも動作確認しておく
組み立て
購入サイトの完成図を見ながら骨組みにサーボを取り付けていく
ネジ+ナット、スポンジ両面テープ、ケーブルタイ等を駆使し、他の部品を盛っていく
プログラム
動作確認時に書いたコードを元にロボット制御用のプログラムを作成する
歩行に関してはこのページを参考にしたけど、難しいことはわからなかったので、角度は現物合わせした
他に気を付ける点は、obnizはI2C5が1つしか使えないため、サーボモータードライバーとカメラで同じI2Cを使うようコードを書く必要がある
コード(ぼちぼち長いので折り畳み)
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://obniz.io/js/jquery-3.2.1.min.js"></script>
<script src="https://unpkg.com/obniz@3.1.0/obniz.js" crossorigin="anonymous"></script>
<style>
@media screen and (orientation: landscape) {
.camera-image {
width: auto;
height: 100%;
right: 0px;
}
}
@media screen and (orientation: portrait) {
.camera-image {
width: 100%;
height: auto;
}
}
body {
user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
.camera-image {
position: absolute;
z-index: 0;
border: 2px;
border-style: solid;
border-color: dimgray;
}
.parent {
height: 100%;
width: 100%;
background-color: darkgray;
position: relative;
}
.moveGroup {
position: absolute;
bottom: 210px;
left: 5px;
z-index: 1;
}
.button {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 100px;
border: 1px solid #000000;
border-radius: 10% 10% 10% 10%;
font-size: xx-large;
opacity: 0.50;
background-color: white;
}
.button-pressed {
background: #EFEFEF;
}
.forward {
position: absolute;
top: 0px;
left: 105px;
}
.turn-left {
position: absolute;
top: 0px;
left: 0px;
}
.turn-right {
position: absolute;
top: 0px;
left: 210px;
}
.left {
position: absolute;
top: 105px;
left: 0px;
}
.right {
position: absolute;
top: 105px;
left: 210px;
}
.backward {
position: absolute;
top: 105px;
left: 105px;
}
</style>
</head>
<body>
<div class="parent">
<div class="moveGroup">
<div class="button forward" id="Forward">↑</div>
<div class="button turn-left" id="TurnLeft">↶</div>
<div class="button turn-right" id="TurnRight">↷</div>
<div class="button left" id="Left">←</div>
<div class="button backward" id="Backward">↓</div>
<div class="button right" id="Right">→</div>
</div>
<img class="camera-image" id="CameraImage">
</div>
<script>
/**
* スリープ
* param {number} msec ミリ秒
*/
const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
var obniz = new Obniz();
obniz.onconnect = async function () {
var i2c = obniz.getFreeI2C();
i2c.start({ mode: "master", sda: 8, scl: 9, clock: 400000, pull: "5v" });
var camera = obniz.wired("ArduCAMMini", { cs: 2, mosi: 3, miso: 4, sclk: 5, gnd: 6, i2c: i2c, module_version: 1 });
await camera.startupWait();
camera.setSize("176x144");
await obniz.wait(1000);
await setInterval(async function () {
let data = await camera.takeWait();
let base64 = camera.arrayToBase64(data);
$("#CameraImage").attr("src", "data:image/jpeg;base64, " + base64);
}, 1000);
var servoDriver = obniz.wired("PCA9685", { i2c: i2c });
var rightFront1Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(0) });
var rightFront2Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(1) });
var rightFront3Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(2) });
var rightRear1Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(4) });
var rightRear2Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(5) });
var rightRear3Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(6) });
var leftRear1Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(8) });
var leftRear2Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(9) });
var leftRear3Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(10) });
var leftFront1Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(12) });
var leftFront2Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(13) });
var leftFront3Servo = obniz.wired("ServoMotor", { pwm: servoDriver.getPWM(14) });
// 各サーボのパルス幅
rightFront1Servo.range = { min: 0.5, max: 2.4 }
rightFront2Servo.range = { min: 0.5, max: 2.4 }
rightFront3Servo.range = { min: 0.5, max: 2.4 }
rightRear1Servo.range = { min: 0.5, max: 2.4 }
rightRear2Servo.range = { min: 0.5, max: 2.4 }
rightRear3Servo.range = { min: 0.5, max: 2.4 }
leftRear1Servo.range = { min: 0.5, max: 2.4 }
leftRear2Servo.range = { min: 0.5, max: 2.4 }
leftRear3Servo.range = { min: 0.5, max: 2.4 }
leftFront1Servo.range = { min: 0.5, max: 2.4 }
leftFront2Servo.range = { min: 0.5, max: 2.4 }
leftFront3Servo.range = { min: 0.5, max: 2.4 }
// 各サーボのトリム値
const rightFront1Trim = -6;
const rightFront2Trim = 0;
const rightFront3Trim = 0;
const rightRear1Trim = 0;
const rightRear2Trim = 0;
const rightRear3Trim = 0;
const leftRear1Trim = -5;
const leftRear2Trim = -2;
const leftRear3Trim = -2;
const leftFront1Trim = 12;
const leftFront2Trim = 7;
const leftFront3Trim = 3;
var isBasePosition = false;
var isForward = false;
var isTurnRight = false;
var isTurnLeft = false;
var isRight = false;
var isLeft = false;
var isBackward = false;
var isStoped = true;
/**
* 右前1角度設定
* param {number} val 角度
*/
const rightFront1Angle = function (val) {
rightFront1Servo.angle(rightFront1Trim + val);
}
/**
* 右前2角度設定
* param {number} val 角度
*/
const rightFront2Angle = function (val) {
rightFront2Servo.angle(180 - (rightFront2Trim + val));
}
/**
* 右前3角度設定
* param {number} val 角度
*/
const rightFront3Angle = function (val) {
rightFront3Servo.angle(180 - (rightFront3Trim + val));
}
/**
* 右後1角度設定
* param {number} val 角度
*/
const rightRear1Angle = function (val) {
rightRear1Servo.angle(180 - (rightRear1Trim + val));
}
/**
* 右後2角度設定
* param {number} val 角度
*/
const rightRear2Angle = function (val) {
rightRear2Servo.angle(rightRear2Trim + val);
}
/**
* 右後3角度設定
* param {number} val 角度
*/
const rightRear3Angle = function (val) {
rightRear3Servo.angle(rightRear3Trim + val);
}
/**
* 左後1角度設定
* param {number} val 角度
*/
const leftRear1Angle = function (val) {
leftRear1Servo.angle(leftRear1Trim + val);
}
/**
* 左後2角度設定
* param {number} val 角度
*/
const leftRear2Angle = function (val) {
leftRear2Servo.angle(180 - (leftRear2Trim + val));
}
/**
* 左後3角度設定
* param {number} val 角度
*/
const leftRear3Angle = function (val) {
leftRear3Servo.angle(180 - (leftRear3Trim + val));
}
/**
* 左前1角度設定
* param {number} val 角度
*/
const leftFront1Angle = function (val) {
leftFront1Servo.angle(180 - (leftFront1Trim + val));
}
/**
* 左前2角度設定
* param {number} val 角度
*/
const leftFront2Angle = function (val) {
leftFront2Servo.angle(leftFront2Trim + val);
}
/**
* 左前3角度設定
* param {number} val 角度
*/
const leftFront3Angle = function (val) {
leftFront3Servo.angle(leftFront3Trim + val);
}
/**
* 右前角度設定
* param {number} val1 角度1
* param {number} val2 角度2
* param {number} val3 角度3
*/
const rightFrontAngle = function (val1, val2, val3) {
rightFront1Angle(val1);
rightFront2Angle(val2);
rightFront3Angle(val3);
}
/**
* 右後角度設定
* param {number} val1 角度1
* param {number} val2 角度2
* param {number} val3 角度3
*/
const rightRearAngle = function (val1, val2, val3) {
rightRear1Angle(val1);
rightRear2Angle(val2);
rightRear3Angle(val3);
}
/**
* 左前角度設定
* param {number} val1 角度1
* param {number} val2 角度2
* param {number} val3 角度3
*/
const leftRearAngle = function (val1, val2, val3) {
leftRear1Angle(val1);
leftRear2Angle(val2);
leftRear3Angle(val3);
}
/**
* 左後角度設定
* param {number} val1 角度1
* param {number} val2 角度2
* param {number} val3 角度3
*/
const leftFrontAngle = function (val1, val2, val3) {
leftFront1Angle(val1);
leftFront2Angle(val2);
leftFront3Angle(val3);
}
/**
* ベースポジション
*/
const basePosition = async function () {
rightFrontAngle(40, 60, 80);
leftFrontAngle(40, 60, 80);
rightRearAngle(40, 60, 80);
leftRearAngle(40, 60, 80);
await sleep(100);
isBasePosition = true;
}
/**
* 前進
*/
const forward = async function () {
while (isForward) {
isStoped = false;
leftRearAngle(40, 90, 100);
await sleep(100);
leftRearAngle(40, 60, 100);
await sleep(100);
leftFrontAngle(50, 90, 65);
await sleep(100);
leftFrontAngle(50, 75, 65);
await sleep(100);
rightFrontAngle(40, 60, 100);
leftFrontAngle(40, 60, 80);
rightRearAngle(50, 75, 65);
leftRearAngle(40, 60, 80);
await sleep(100);
rightRearAngle(40, 90, 100);
await sleep(100);
rightRearAngle(40, 60, 100);
await sleep(100);
rightFrontAngle(50, 90, 65);
await sleep(100);
rightFrontAngle(50, 75, 65);
await sleep(100);
rightFrontAngle(40, 60, 80);
leftFrontAngle(40, 60, 100);
rightRearAngle(40, 60, 80);
leftRearAngle(50, 75, 65);
await sleep(100);
isBasePosition = false;
}
isStoped = true;
}
/**
* 右旋回
*/
const turnRight = async function () {
while (isTurnRight) {
isStoped = false;
rightFrontAngle(40, 90, 110);
await sleep(100);
rightFrontAngle(40, 60, 110);
await sleep(100);
leftRearAngle(40, 90, 110);
await sleep(100);
leftRearAngle(40, 60, 110);
await sleep(100);
rightRearAngle(40, 90, 50);
await sleep(100);
rightRearAngle(40, 60, 50);
await sleep(100);
leftFrontAngle(40, 90, 50);
await sleep(100);
leftFrontAngle(40, 60, 50);
await sleep(100);
basePosition();
}
isStoped = true;
}
/**
* 左旋回
*/
const turnLeft = async function () {
while (isTurnLeft) {
isStoped = false;
leftFrontAngle(40, 90, 110);
await sleep(100);
leftFrontAngle(40, 60, 110);
await sleep(100);
rightRearAngle(40, 90, 110);
await sleep(100);
rightRearAngle(40, 60, 110);
await sleep(100);
leftRearAngle(40, 90, 50);
await sleep(100);
leftRearAngle(40, 60, 50);
await sleep(100);
rightFrontAngle(40, 90, 50);
await sleep(100);
rightFrontAngle(40, 60, 50);
await sleep(100);
basePosition();
}
isStoped = true;
}
/**
* 右移動
*/
const right = async function () {
while (isRight) {
isStoped = false;
rightFrontAngle(60, 90, 80);
await sleep(100);
rightFrontAngle(60, 80, 80);
await sleep(100);
rightRearAngle(40, 90, 80);
await sleep(100);
rightRearAngle(60, 80, 80);
await sleep(100);
leftFrontAngle(20, 90, 80);
await sleep(100);
leftFrontAngle(20, 40, 80);
await sleep(100);
leftRearAngle(20, 90, 80);
await sleep(100);
leftRearAngle(20, 40, 80);
await sleep(100);
basePosition();
}
isStoped = true;
}
/**
* 左移動
*/
const left = async function () {
while (isLeft) {
isStoped = false;
leftFrontAngle(60, 90, 80);
await sleep(100);
leftFrontAngle(60, 80, 80);
await sleep(100);
leftRearAngle(40, 90, 80);
await sleep(100);
leftRearAngle(60, 80, 80);
await sleep(100);
rightFrontAngle(20, 90, 80);
await sleep(100);
rightFrontAngle(20, 40, 80);
await sleep(100);
rightRearAngle(20, 90, 80);
await sleep(100);
rightRearAngle(20, 40, 80);
await sleep(100);
basePosition();
}
isStoped = true;
}
/**
* 後退
*/
const backward = async function () {
while (isBackward) {
isStoped = false;
if (isBasePosition) {
leftFrontAngle(40, 90, 100);
await sleep(100);
leftFrontAngle(40, 60, 100);
await sleep(100);
leftRearAngle(50, 90, 65);
await sleep(100);
leftRearAngle(50, 75, 65);
await sleep(100);
}
rightFrontAngle(50, 75, 65);
leftFrontAngle(40, 60, 80);
rightRearAngle(40, 60, 100);
leftRearAngle(40, 60, 80);
await sleep(100);
rightFrontAngle(40, 90, 100);
await sleep(100);
rightFrontAngle(40, 60, 100);
await sleep(100);
rightRearAngle(50, 90, 65);
await sleep(100);
rightRearAngle(50, 75, 65);
await sleep(100);
rightFrontAngle(40, 60, 80);
leftFrontAngle(50, 75, 65);
rightRearAngle(40, 60, 80);
leftRearAngle(40, 60, 100);
await sleep(100);
leftFrontAngle(40, 90, 100);
await sleep(100);
leftFrontAngle(40, 60, 100);
await sleep(100);
leftRearAngle(50, 90, 65);
await sleep(100);
leftRearAngle(50, 75, 65);
await sleep(100);
isBasePosition = false;
}
isStoped = true;
}
basePosition();
/**
* 前進ボタン
*/
$("#Forward").on("mousedown touchstart", async function () {
$(this).addClass("button-pressed");
if (isStoped) {
isForward = true;
await forward();
}
}).on("mouseup mouseleave touchend", async function () {
$(this).removeClass("button-pressed");
isForward = false;
});
/**
* 右旋回ボタン
*/
$("#TurnRight").on("mousedown touchstart", async function () {
$(this).addClass("button-pressed");
if (isStoped) {
isTurnRight = true;
turnRight();
}
}).on("mouseup mouseleave touchend", async function () {
$(this).removeClass("button-pressed");
isTurnRight = false;
});
/**
* 左旋回ボタン
*/
$("#TurnLeft").on("mousedown touchstart", async function () {
$(this).addClass("button-pressed");
if (isStoped) {
isTurnLeft = true;
turnLeft();
}
}).on("mouseup mouseleave touchend", async function () {
$(this).removeClass("button-pressed");
isTurnLeft = false;
});
/**
* 右移動ボタン
*/
$("#Right").on("mousedown touchstart", async function () {
$(this).addClass("button-pressed");
if (isStoped) {
isRight = true;
right();
}
}).on("mouseup mouseleave touchend", async function () {
$(this).removeClass("button-pressed");
isRight = false;
});
/**
* 左移動ボタン
*/
$("#Left").on("mousedown touchstart", async function () {
$(this).addClass("button-pressed");
if (isStoped) {
isLeft = true;
left();
}
}).on("mouseup mouseleave touchend", async function () {
$(this).removeClass("button-pressed");
isLeft = false;
});
/**
* 後退ボタン
*/
$("#Backward").on("mousedown touchstart", async function () {
$(this).addClass("button-pressed");
if (isStoped) {
isBackward = true;
backward();
}
}).on("mouseup mouseleave touchend", async function () {
$(this).removeClass("button-pressed");
isBackward = false;
});
}
</script>
</body>
</html>
完成
スマホでの操作画面、コントローラーとカメラ映像ももちろん表示!
動画
https://twitter.com/yamazin/status/1202192900198518786
感想
obnizは簡単に電子工作できるのでオススメ!
サーボモーター1つの価格は大したことないけど12個も使うと結構高い…