マイクロビットで動きを制御する、二足歩行ロボットをつくります。
第一回の記事(こちら)では、どのようなロボットにするかを考え、それを踏まえて、マイクロビットで4つのサーボモーターを制御できることを確認し、回路構成を決定しました。
第二回の記事(こちら)では、3Dプリンタを使って、実際にロボットの筐体をつくり、組み立てました。
第三回の記事(こちら)では、マイクロビットのプログラムをつくり、ロボットをまっすぐ歩かせてみました。
いよいよ今回は、マイクロビットの「無線」機能を使い、このロボットを、もうひとつのマイクロビットから無線操縦できるようにしたいと思います。
送信機用として、マイクロビットをもう1台、および「Mi:電源ボード」を用意します。
送信機用プログラム
送信機用のマイクロビットには、以下のプログラムを書き込みます。
受信側マイクロビットと無線通信できるようにするため、最初に「無線のグループを設定」します。
「AボタンとBボタンを同時に押したとき」、「Aボタンのみを押したとき」、「Bボタンのみを押したとき」、「ボタンを押していないとき」のそれぞれの状態のとき、無線で「1」、「2」、「3」、「0」の数値を送信します。
「AボタンとBボタンを同時に押したとき(1)」に直進、「Aボタンのみを押したとき(2)」に方向転換(左)、「Bボタンのみを押したとき(3)」に方向転換(右)、「ボタンを押していないとき(0)」に停止させることにします。
なお、「ずっと」ブロックの最後に、「一時停止」を入れています。
「一時停止」がない場合は、A、Bボタンを同時に押しても、そのわずかな時間差を読み取って、方向転換の処理に入ってしまいます。それを回避するために入れているものです。
画像だけだと見にくいので、JavaScriptのコードも掲載しておきます。
radio.setGroup(1)
basic.forever(function () {
if (input.buttonIsPressed(Button.A) && input.buttonIsPressed(Button.B)) {
radio.sendNumber(1)
} else if (input.buttonIsPressed(Button.A)) {
radio.sendNumber(2)
} else if (input.buttonIsPressed(Button.B)) {
radio.sendNumber(3)
} else {
radio.sendNumber(0)
}
basic.pause(100)
})
受信機用プログラム
受信機用のプログラムは結構複雑になりました。
これらはサーボモータを動かすための関数群です。
「move_servo」で、「サーボ番号」で指定したサーボモータを指定した角度に動かします。
「cnt_servo」で、指定したサーボモータを、「角度1」から「角度2」に変化させます。ロボットの動きを少しでもなめらかにするために、角度は1度ずつ変化させるようにし、1度変化するたびに若干停止させるようにしています。
「cnt_2servo」は「cnt_servo」と」同様の処理を行いますが、同時にふたつのサーボモータを動かすときに使います。
これらは実際のロボットの動きを定義している関数群です。
「straight」、「left」、「right」で、直進、方向転換(左)、方向転換(右)を行うための、各サーボモーターの動きを定義しています。
「straight_start」は、両足がそろっている状態から歩き出すまでの動きを定義した関数です。「straight」を実行する際、そのひとつ前の動きが「straight」でなければ、最初にこの関数を実行します。
ひとつ前の動きは「prev」という変数に格納されています。
「straight_stop」は、歩いている状態から両足をそろえるまでの動きを定義した関数です。「left」、「right」、「stop」を実行する際、そのひとつ前の動きが「straight」であれば、最初にこの関数を実行します。
これらが、実際にロボットを動かすプログラムです。
「init_angle*」で、各サーボモータの角度の初期値(直立している時の角度)を配列「angle」に設定しています。
この値はロボットの個体それぞれに対して調整を行い、異なる値を設定することになります。
「最初だけ」ブロックでは、まず最初に、無線のグループを、送信機と同じ番号に設定します。
「flag」というのは、送信機から受信した数値を格納する変数で、最初に、この値を「0」に設定しておきます。サーボモータの角度の初期値を設定するための「init_angle*」関数も呼び出します。
各サーボモータを順番に動かし、初期値にします。これでロボットは直立状態になります。
処理が終わったら、LEDにアイコンを表示します。
「無線を受信したとき」では、随時、無線で受信した番号を「flag」変数に書き込んでいきます。
「ずっと」では、「flag」の値に応じて、「straight」、「left」、「right」、「stop」の関数を呼び出します。
動作が終わると、ひとつ前の動きを表す「prev」変数の値を更新します。
JavaScriptのコードでは以下のようになります。
function move_servo (サーボ番号: number, 角度: number) {
if (サーボ番号 == 0) {
pins.servoWritePin(AnalogPin.P0, 角度)
}
if (サーボ番号 == 1) {
pins.servoWritePin(AnalogPin.P1, 角度)
}
if (サーボ番号 == 2) {
pins.servoWritePin(AnalogPin.P2, 角度)
}
if (サーボ番号 == 3) {
pins.servoWritePin(AnalogPin.P8, 角度)
}
}
function cnt_servo (サーボ番号: number, 角度1: number, 角度2: number) {
if (角度1 < 角度2) {
for (let index3 = 0; index3 <= 角度2 - 角度1; index3++) {
move_servo(サーボ番号, angle[サーボ番号] + 角度1 + index3)
basic.pause(15)
}
} else {
for (let index4 = 0; index4 <= 角度1 - 角度2; index4++) {
move_servo(サーボ番号, angle[サーボ番号] + 角度1 - index4)
basic.pause(15)
}
}
}
function cnt_2servo (サーボ番号1: number, サーボ番号2: number, 角度1: number, 角度2: number) {
if (角度1 < 角度2) {
for (let index = 0; index <= 角度2 - 角度1; index++) {
move_servo(サーボ番号1, angle[サーボ番号1] + 角度1 + index)
move_servo(サーボ番号2, angle[サーボ番号2] + 角度1 + index)
basic.pause(15)
}
} else {
for (let index2 = 0; index2 <= 角度1 - 角度2; index2++) {
move_servo(サーボ番号1, angle[サーボ番号1] + 角度1 - index2)
move_servo(サーボ番号2, angle[サーボ番号2] + 角度1 - index2)
basic.pause(15)
}
}
}
function straight () {
if (prev != 1) {
straight_start()
}
cnt_servo(0, 0, -30)
cnt_servo(2, 0, 25)
cnt_2servo(1, 3, -25, 25)
cnt_servo(2, 25, 0)
cnt_servo(0, -30, 0)
cnt_servo(2, 0, 30)
cnt_servo(0, 0, -25)
cnt_2servo(3, 1, 25, -25)
cnt_servo(0, -25, 0)
cnt_servo(2, 30, 0)
}
function left () {
if (prev == 1) {
straight_stop()
}
cnt_servo(2, 0, 30)
cnt_servo(0, 0, -25)
cnt_servo(3, 0, -25)
cnt_servo(0, -25, 0)
cnt_servo(2, 30, 0)
cnt_servo(0, 0, -30)
cnt_servo(2, 0, 25)
cnt_servo(3, -25, 0)
cnt_servo(2, 25, 0)
cnt_servo(0, -30, 0)
}
function right () {
if (prev == 1) {
straight_stop()
}
cnt_servo(0, 0, -30)
cnt_servo(2, 0, 25)
cnt_servo(1, 0, 25)
cnt_servo(2, 25, 0)
cnt_servo(0, -30, 0)
cnt_servo(2, 0, 30)
cnt_servo(0, 0, -25)
cnt_servo(1, 25, 0)
cnt_servo(0, -25, 0)
cnt_servo(2, 30, 0)
}
function stop () {
if (prev == 1) {
straight_stop()
}
}
function straight_start () {
cnt_servo(2, 0, 30)
cnt_servo(0, 0, -25)
cnt_2servo(3, 1, 0, -25)
cnt_servo(0, -25, 0)
cnt_servo(2, 30, 0)
}
function straight_stop () {
cnt_servo(0, 0, -30)
cnt_servo(2, 0, 25)
cnt_2servo(1, 3, -25, 0)
cnt_servo(2, 25, 0)
cnt_servo(0, -30, 0)
}
function init_angleA () {
angle = [85, 92, 69, 89]
}
function init_angleB () {
angle = [54, 66, 59, 58]
}
radio.onReceivedNumber(function (receivedNumber) {
flag = receivedNumber
})
let prev = 0
let angle: number[] = []
let flag = 0
radio.setGroup(1)
flag = 0
init_angleB()
for (let サーボ番号 = 0; サーボ番号 <= 3; サーボ番号++) {
basic.showNumber(3 - サーボ番号)
move_servo(サーボ番号, angle[サーボ番号])
basic.pause(1000)
}
basic.showIcon(IconNames.Yes)
basic.pause(1000)
basic.showIcon(IconNames.Happy)
basic.forever(function () {
if (flag == 1) {
straight()
prev = 1
} else if (flag == 2) {
left()
prev = 2
} else if (flag == 3) {
right()
prev = 3
} else {
stop()
prev = 0
}
})
動作確認
このプログラムを動かした結果は以下のとおりです。
動きはかなり頼りないですが、ともかく送信機で操縦して、動かすことができるようになりました。
もっとなめらかに自然に動かすためには、ハード面、ソフト面ともに、色々とやるべきことがありそうで、ロボットは非常に奥が深いことを実感しました。
ただ、今回はこれで、当初の目標を一旦達成できましたので、ここまでで完了とします。
なお、私がマイクロビットの使い方を習得するのにあたっては、以下の書籍を参考にさせていただきました。
初心者向けから、比較的高度なものまで、さまざまな情報が記載されているだけでなく、子供向けの作例も多数掲載されていますので、「プログラミング教育」のための題材さがしなどにもおすすめです。