
Raspberry Pi Picoを利用したミニ相撲ロボット 電気的な部分とプログラム ラジコン型&自立型
『ミニ相撲ロボット研究家かく語りき』の公開によって、自称・ミニ相撲ロボット研究者となった私。電子工作の経験があれば、ミニ相撲ロボットの基板を作ることなんて大したことではないが、未経験の者、あるいはまだ経験の少ない者が取り組むには少し難易度が高いように思える。そこで、あくまでも一例として、私の妄想を共有する。
なぜ、初心者にはPicoなのか。この理由も説明しておこう。それは考えることが比較的少ないからである。マイコンそのものを用意し、1からミニ相撲ロボット用にマイコンボードを設計、プリント基板として量産 ーというのが理想的ではある。コスト、ロボットの空きスペース、面白さ。これらに富んだ手法だ。しかし、それなりの知識や時間が求められる。一方、既存のマイコンボートに付け足していくように基盤を設計すれば、マイコンそのもの仕様について熟慮する手間が省けるのだ。その分コストはかかってしまうが、それも考えようである。例えば、AVRマイコンとして汎用されるAtmega328P-PUの値段は400円前後であり、Picoはというと800円前後である。パソコンとマイコンとをUSBで接続する場合、Atmega328P-PUにはUSBシリアル変換モジュールが必要となり、これに加えて500円前後の費用が重なってしまうことになる。もはやコスト面ではあまり差がないと云える。AVRマイコンとARM(Pico 2はRISC-Vも搭載)マイコンとを比較するのは少々乱雑な思考であるように思えるが、動いてしまえばどちらも同じようなものだ。
部品の検討
今回はお財布に優しいN20のモーターを使用。N20モーターに適した部品を集める。
ラジコン型と自立型の双方に必要なもの
マイコン:今回はPicoかPico2。どちらでも。
バッテリー:2セルで大体300mAh前後のもの。3セルでも問題ない。
モータードライバ:今回は「TB6612モータードライバー ブレークアウトボードキット」を利用する。モジュールによってピンの配置は全く異なるため注意が必要。
モーター:N20のもの。タイヤの口径にもよるが、500rpm程度は欲しい。Aliexpressならココとかココとかココ、ココもよさそう。ロボショップなら500rpmのこれかな。ただし定格電圧が12Vである点に注意。
3端子レギュレーター:出力が3.3Vのもの。出力電流は1A程度で必要十二分。
お好みのケーブルや端子:T字, XT字, JSTプラグ..など
ラジコン型のみ
自立型のみ
距離センサー:相手を捉えるためのセンサー。おすすめは「JS40F」。茶色い線には3.3Vを、青い線はGNDに、そして黒い線はGPIOに。相手のロボットに反応していればHIGH、反応していなければLOWになる。便利なことに、内蔵されている赤いLEDを見れば人間の目でも反応しているのかどうか判断できる。
フォトリフレクタ:この中でもラインセンサーと呼ばれるモジュール。おすすめは「ML1」。5Vとと書かれているが、Jsumoの販売ページによれば3.3Vでも動作するとのこと。5V+に3.3Vを、GNDをGNDに、そしてSignalはGPIOに。明るいほど値は小さくなり、暗いほど値は大きくなる。アナログ入力に対応しているGPIOは、GP26、GP27、GP28の3つだけである点に注意。Picoのアナログピンは意外と少ない。
スタートモジュール:SONY製のリモコンの信号を受信するJsumoのモジュール。Ready, Start, Stopの3パターンが用意される。詳しくはデーターシートを参照されたいが、マイコンへの入力(s)はReady時とStop時はLOW、Start時はHIGHとなる。+に3.3Vを、何も書かれていないスルーホールをGNDに、そしてsをGPIOに。
各部品を組み合わせる

あくまでも大雑把な図。例えば3端子レギュレーターにバイパスコンデンサがない。好みで電源スイッチやセンサーを追加したり、自立型ならスタートモジュールを、ラジコン型ならプロポのレシーバーをつけたりする。自立型には当然プロポのレシーバーをつけてはならないが、ラジコン型にセンサーを付けるのは問題ない。
「TB6612モータードライバー ブレークアウトボードキット」について補足。回路図、ピン配置図には、J1やJ2に関しての記述がある。それぞれをショートさせるか否かは、基板の設計やプログラムの組み方を考えて諸個人で判断してもらいたい。図1のようにPGをGNDに接続しない場合はJ2をショートさせること。ICのデーターシートを参照すればわかることだが、A_1,A_2,B_1,B_2にはデジタル、A_P,B_PにはPWM(ArduinoならanalogWrite関数が便利)を出力する。回転の向きを制御するのがA_1,A_2,B_1,B_2であり、回転数を制御するのがA_P,B_Pである。
図が汚いなんて言わないで… 初心者でもわかるように作ろうと頑張った。足が3本生えていて電圧を7.4Vから3.3Vに降圧しているのが3端子レギュレーター。
プログラム(Arduino IDEで書き込む場合)
必要に応じて自作関数にまとめるといいだろう。プログラムのライセンスはCC0。Jsumoが公開しているプログラムも参考になるだろう。
以下のプログラムは全て常にスタンバイモードとなっている前提で制作した。そのため必ずJ1はショートさせること。
※以下のプログラムは全て動作未確認です。いかなる損害が発生しようと私は一切責任を負いません。
動作確認用の基本的なモーター制御
//ピンの指定
const int A_1 = 3;
const int A_2 = 4;
const int A_P = 5;
const int B_1 = 2;
const int B_2 = 1;
const int B_P = 0;
void setup() {
for (int i = 0; i < 6; i++) pinMode(i, OUTPUT); //GP0からGP5の6つのピンはすべて出力に設定
}
void loop() {
//モーターAを3秒間正転させる
digitalWrite(A_1, HIGH);
digitalWrite(A_2, LOW);
analogWrite(A_P, 255); //0から255の範囲で回転数を調整できる
delay(3000);
//モーターAを3秒間逆転させる
digitalWrite(A_1, LOW);
digitalWrite(A_2, HIGH);
analogWrite(A_P, 255); //0から255の範囲で回転数を調整できる
delay(3000);
//モーターAにショートブレーキをかけ3秒間待つ
digitalWrite(A_1, HIGH);
digitalWrite(A_2, HIGH);
delay(3000);
//モーターBを3秒間正転させる
digitalWrite(B_1, HIGH);
digitalWrite(B_2, LOW);
analogWrite(B_P, 255); //0から255の範囲で回転数を調整できる
delay(3000);
//モーターBを3秒間逆転させる
digitalWrite(B_1, LOW);
digitalWrite(B_2, HIGH);
analogWrite(B_P, 255); //0から255の範囲で回転数を調整できる
delay(3000);
//モーターBにショートブレーキをかけ3秒間待つ
digitalWrite(B_1, HIGH);
digitalWrite(B_2, HIGH);
delay(3000);
//モーターAとBを3秒間ストップする(自動車でいうとニュートラル?)
digitalWrite(A_1, LOW);
digitalWrite(A_2, LOW);
digitalWrite(B_1, LOW);
digitalWrite(B_2, LOW);
delay(3000);
}
自立型用
brake関数はvoid loop内で使用されていない。動きを変える前にショートブレーキを踏むようにすると、より正確な制御ができるかもしれない。
/*
谷口 左千夫によるミニ相撲ロボット自立型用プログラム
このプログラムには含まれていないが、DIPスイッチを用いて試合中にいくつかのパターンのプログラムを選択できるようにするとよいだろう。
また、赤外線センサーが1つだけでは足りないだろう。数を増やしてより複雑な動作をさせられるようにするべきだ。
ライセンス:CC0
*/
//ピンの指定
//モーターA
const int A_1 = 3;
const int A_2 = 4;
const int A_P = 5;
//モーターB
const int B_1 = 2;
const int B_2 = 1;
const int B_P = 0;
//スタートモジュール
const int microStartModule = 6;
//正面に配置した「JS40F」
const int flontDistanceSensor = 7;
//底面の右上に配置した「ML1」
const int leftLineSensor = 26;
//底面の左上に配置した「ML1」
const int rightLineSensor = 27;
void setup() {
for (int i = 0; i < 6; i++) pinMode(i, OUTPUT); //GP0からGP5の6本のピンはすべて出力に設定
for (int i = 6; i < 23; i++) pinMode(i, INPUT); //GP6からGP22の17本のピンはすべて入力に設定
for (int i = 26; i < 29; i++) pinMode(i, INPUT); //GP26からGP28の3本のピンはすべて入力に設定
}
void loop() {
//Ready,Stop時はLOW
while (microStartModule == LOW)
;
//初めに黒い部分の値を記録する 0.9倍することで誤作動を防ぐ
int leftLineSensorBlack = analogRead(leftLineSensor) * 0.9;
int rightLineSensorBlack = analogRead(rightLineSensor) * 0.9;
//Start時はHIGH
while (microStartModule == HIGH) {
//ラインセンサーを確認
//両方が白い線に触れたとき
if (leftLineSensor < leftLineSensorBlack && rightLineSensor < rightLineSensorBlack) {
//500msバックする
motor(-100, -100);
delay(500);
//2000ms旋回(ロボットの正面を土俵の内側に向ける)
motor(100, -100);
delay(2000);
}
//左のみ
else if (leftLineSensor < leftLineSensorBlack && rightLineSensor > rightLineSensorBlack) {
//500msバックする
motor(-100, -50); //右は白い線に触れていないため出力を弱める
delay(500);
//2000ms旋回(ロボットの正面を土俵の内側に向ける)
motor(100, -100);
delay(2000);
}
//右のみ
else if (leftLineSensor > leftLineSensorBlack && rightLineSensor < rightLineSensorBlack) {
//500msバックする
motor(-50, -100); //右は白い線に触れていないため出力を弱める
delay(500);
//2000ms旋回(ロボットの正面を土俵の内側に向ける)
motor(100, -100);
delay(2000);
}
//赤外線センサーを確認
//正面
else if (flontDistanceSensor == HIGH && leftLineSensor > leftLineSensorBlack && rightLineSensor > rightLineSensorBlack) {
motor(100, 100); //直進する
}
//相手のロボットを探す動作
else if (leftLineSensor > leftLineSensorBlack && rightLineSensor > rightLineSensorBlack) {
motor(20, 20); //ゆっくり直進する
}
}
}
/*
モーターを制御するための関数
引数「motorA」「motorB」に-100~100の値を入れることで回転数を0%から100%の範囲で制御(マイナスなら逆転)
*/
void motor(int motorA, int motorB) {
//モーターA
if (motorA > 0) {
motorA = map(motorA, 0, 100, 0, 255); //analogWrite関数の引数の0~255に合わせる
analogWrite(A_P, motorA); //PWMを変更
//正転
digitalWrite(A_1, HIGH);
digitalWrite(A_2, LOW);
} else if (motorA == 0) {
//ストップ
digitalWrite(A_1, LOW);
digitalWrite(A_2, LOW);
} else {
motorA -= motorA; //0以下の数字であるためマイナスをかけ絶対値にする
motorA = map(motorA, 0, 100, 0, 255); //analogWrite関数の引数の0~255に合わせる
analogWrite(A_P, motorA); //PWMを変更
//逆転
digitalWrite(A_1, LOW);
digitalWrite(A_2, HIGH);
}
//モーターB
if (motorB > 0) {
motorB = map(motorB, 0, 100, 0, 255); //analogWrite関数の引数の0~255に合わせる
analogWrite(B_P, motorB); //PWMを変更
//正転
digitalWrite(B_1, HIGH);
digitalWrite(B_2, LOW);
} else if (motorB == 0) {
//ストップ
digitalWrite(B_1, LOW);
digitalWrite(B_2, LOW);
} else {
motorB -= motorB; //0以下の数字であるためマイナスをかけ絶対値にする
motorB = map(motorB, 0, 100, 0, 255); //analogWrite関数の引数の0~255に合わせる
analogWrite(B_P, motorB); //PWMを変更
//逆転
digitalWrite(B_1, LOW);
digitalWrite(B_2, HIGH);
}
}
/*
モーターの回転を止めるための関数
両方のモーターにショートブレーキをかける
*/
void brake() {
digitalWrite(A_1, HIGH);
digitalWrite(A_2, HIGH);
digitalWrite(B_1, HIGH);
digitalWrite(B_2, HIGH);
}
プロポのPWM

ラジコン型用(割り込み不使用,while文)
brake関数はvoid loop内で使用されていないが、好みでスティックが中央のときにショートブレーキを踏むようにするといいかもしれない。
パルス幅の測定にはwhile文が使われている。pulseIn関数を使用するよりもプログラムは助長なものになってしまう。while文とpulseIn関数のどちらがより高速な処理なのかは不明。有識者求む。
/*
谷口 左千夫によるミニ相撲ロボットラジコン型用プログラム
ライセンス:CC0
*/
//ピンの指定
//モーターA
const int A_1 = 3;
const int A_2 = 4;
const int A_P = 5;
//モーターB
const int B_1 = 2;
const int B_2 = 1;
const int B_P = 0;
//近藤科学製プロポ「MC-8」のレシーバー このプログラムではCH-A,CH-C(スティックの上下)のみを使用する
const int chA = 6; //左
const int chC = 7; //右
/*
パルス幅の仕様
CH-A~D:890-1496-2100(μs)
890,1496,2100がそれぞれ 一番下,真ん中,一番上となる
真ん中できれいに1496μsのパルス幅を導き出すのは難しい
誤差を考慮しこのプログラムでは890-1476~1516-2100とする
*/
void setup() {
for (int i = 0; i < 6; i++) pinMode(i, OUTPUT); //GP0からGP5の6本のピンはすべて出力に設定
for (int i = 6; i < 8; i++) pinMode(i, INPUT); //GP6,GP7の2本のピンは入力に設定
}
unsigned long pulseStart; //パルスの開始時間を記録
unsigned long pulse[2]; //パルス幅を記録
int motorA, motorB; //-255~255に変換されたパルス幅を記録
void loop() {
//chAのパルス幅を調べる
while (digitalRead(chA) == HIGH)
; //LOWになる(パルスが終了する)まで待つ
while (digitalRead(chA) == LOW)
; //HIGHになる(パルスが開始する)まで待つ
pulseStart = micros(); //パスルの開始時間を記録
while (digitalRead(chA) == HIGH)
; //LOWになる(パルスが終了する)まで待つ
pulse[0] = micros() - pulseStart; //パルス幅 = パスルの終了時間「micros()」 - パスルの開始時間「pulseStart」
//chCのパルス幅を調べる
while (digitalRead(chC) == HIGH)
; //LOWになる(パルスが終了する)まで待つ
while (digitalRead(chC) == LOW)
; //HIGHになる(パルスが開始する)まで待つ
pulseStart = micros(); //パスルの開始時間を記録
while (digitalRead(chC) == HIGH)
; //LOWになる(パルスが終了する)まで待つ
pulse[1] = micros() - pulseStart; //パルス幅 = パスルの終了時間「micros()」 - パスルの開始時間「pulseStart」
//chAのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[0] > 1516) {
motorA = map(pulse[0], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[0] < 1476) {
motorA = map(pulse[0], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorA = 0;
//chCのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[1] > 1516) {
motorB = map(pulse[1], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[1] < 1476) {
motorB = map(pulse[1], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorB = 0;
motor(motorA, motorB); //パルス幅を元にモーターを回転させる
}
/*
モーターを制御するための関数
引数「motorA」「motorB」に-255~255の値を入れることで制御(マイナスなら逆転)
*/
void motor(int motorA, int motorB) {
//モーターA
if (motorA > 0) {
analogWrite(A_P, motorA); //PWMを変更
//正転
digitalWrite(A_1, HIGH);
digitalWrite(A_2, LOW);
} else if (motorA == 0) {
//ストップ
digitalWrite(A_1, LOW);
digitalWrite(A_2, LOW);
} else {
motorA -= motorA; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(A_P, motorA); //PWMを変更
//逆転
digitalWrite(A_1, LOW);
digitalWrite(A_2, HIGH);
}
//モーターB
if (motorB > 0) {
analogWrite(B_P, motorB); //PWMを変更
//正転
digitalWrite(B_1, HIGH);
digitalWrite(B_2, LOW);
} else if (motorB == 0) {
//ストップ
digitalWrite(B_1, LOW);
digitalWrite(B_2, LOW);
} else {
motorB -= motorB; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(B_P, motorB); //PWMを変更
//逆転
digitalWrite(B_1, LOW);
digitalWrite(B_2, HIGH);
}
}
/*
モーターの回転を止めるための関数
両方のモーターにショートブレーキをかける
*/
void brake() {
digitalWrite(A_1, HIGH);
digitalWrite(A_2, HIGH);
digitalWrite(B_1, HIGH);
digitalWrite(B_2, HIGH);
}
ラジコン型用(割り込み不使用,pulseIn関数)
brake関数はvoid loop内で使用されていないが、好みでスティックが中央のときにショートブレーキを踏むようにするといいかもしれない。
パルス幅の測定にはpulseIn関数が使われている。pulseIn関数を使用するとwhile文に比べより簡潔になる。while文とpulseIn関数のどちらがより高速な処理なのかは不明。有識者求む。
/*
谷口 左千夫によるミニ相撲ロボットラジコン型用プログラム
ライセンス:CC0
*/
//ピンの指定
//モーターA
const int A_1 = 3;
const int A_2 = 4;
const int A_P = 5;
//モーターB
const int B_1 = 2;
const int B_2 = 1;
const int B_P = 0;
//近藤科学製プロポ「MC-8」のレシーバー このプログラムではCH-A,CH-C(スティックの上下)のみを使用する
const int chA = 6; //左
const int chC = 7; //右
/*
パルス幅の仕様
CH-A~D:890-1496-2100(μs)
890,1496,2100がそれぞれ 一番下,真ん中,一番上となる
真ん中できれいに1496μsのパルス幅を導き出すのは難しい
誤差を考慮しこのプログラムでは890-1476~1516-2100とする
*/
void setup() {
for (int i = 0; i < 6; i++) pinMode(i, OUTPUT); //GP0からGP5の6本のピンはすべて出力に設定
for (int i = 6; i < 8; i++) pinMode(i, INPUT); //GP6,GP7の2本のピンは入力に設定
}
unsigned long pulse[2]; //パルス幅を記録
int motorA, motorB; //-255~255に変換されたパルス幅を記録
void loop() {
//chAのパルス幅を調べる
pulse[0] = pulseIn(chA, HIGH); //HIGHの時間を測りpulse[0]に記録
//chCのパルス幅を調べる
pulse[1] = pulseIn(chC, HIGH); //HIGHの時間を測りpulse[1]に記録
//chAのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[0] > 1516) {
motorA = map(pulse[0], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[0] < 1476) {
motorA = map(pulse[0], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorA = 0;
//chCのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[1] > 1516) {
motorB = map(pulse[1], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[1] < 1476) {
motorB = map(pulse[1], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorB = 0;
motor(motorA, motorB); //パルス幅を元にモーターを回転させる
}
/*
モーターを制御するための関数
引数「motorA」「motorB」に-255~255の値を入れることで制御(マイナスなら逆転)
*/
void motor(int motorA, int motorB) {
//モーターA
if (motorA > 0) {
analogWrite(A_P, motorA); //PWMを変更
//正転
digitalWrite(A_1, HIGH);
digitalWrite(A_2, LOW);
} else if (motorA == 0) {
//ストップ
digitalWrite(A_1, LOW);
digitalWrite(A_2, LOW);
} else {
motorA -= motorA; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(A_P, motorA); //PWMを変更
//逆転
digitalWrite(A_1, LOW);
digitalWrite(A_2, HIGH);
}
//モーターB
if (motorB > 0) {
analogWrite(B_P, motorB); //PWMを変更
//正転
digitalWrite(B_1, HIGH);
digitalWrite(B_2, LOW);
} else if (motorB == 0) {
//ストップ
digitalWrite(B_1, LOW);
digitalWrite(B_2, LOW);
} else {
motorB -= motorB; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(B_P, motorB); //PWMを変更
//逆転
digitalWrite(B_1, LOW);
digitalWrite(B_2, HIGH);
}
}
/*
モーターの回転を止めるための関数
両方のモーターにショートブレーキをかける
*/
void brake() {
digitalWrite(A_1, HIGH);
digitalWrite(A_2, HIGH);
digitalWrite(B_1, HIGH);
digitalWrite(B_2, HIGH);
}
ラジコン型用(割り込み使用)
brake関数はvoid loop内で使用されていないが、好みでスティックが中央のときにショートブレーキを踏むようにするといいかもしれない。
/*
谷口 左千夫によるミニ相撲ロボットラジコン型用プログラム
ライセンス:CC0
*/
//ピンの指定
//モーターA
const int A_1 = 3;
const int A_2 = 4;
const int A_P = 5;
//モーターB
const int B_1 = 2;
const int B_2 = 1;
const int B_P = 0;
//近藤科学製プロポ「MC-8」のレシーバー このプログラムではCH-A,CH-C(スティックの上下)のみを使用する
const int chA = 6; //左
const int chC = 7; //右
/*
パルス幅の仕様
CH-A~D:890-1496-2100(μs)
890,1496,2100がそれぞれ 一番下,真ん中,一番上となる
真ん中できれいに1496μsのパルス幅を導き出すのは難しい
誤差を考慮しこのプログラムでは890-1476~1516-2100とする
*/
volatile unsigned long pulseStart[2]; //パルスの開始時間を記録
volatile unsigned long pulse[2]; //パルス幅を記録
void setup() {
for (int i = 0; i < 6; i++) pinMode(i, OUTPUT); //GP0からGP5の6本のピンはすべて出力に設定
for (int i = 6; i < 8; i++) pinMode(i, INPUT); //GP6,GP7の2本のピンは入力に設定
attachInterrupt(chA, interruptMotorA, CHANGE); //chAがHIGHかLOWに変化したときに割り込み
attachInterrupt(chC, interruptMotorB, CHANGE); //chCがHIGHかLOWに変化したときに割り込み
}
int motorA, motorB; //-255~255に変換されたパルス幅を記録
void loop() {
//chAのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[0] > 1516) {
motorA = map(pulse[0], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[0] < 1476) {
motorA = map(pulse[0], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorA = 0;
//chCのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[1] > 1516) {
motorB = map(pulse[1], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[1] < 1476) {
motorB = map(pulse[1], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorB = 0;
motor(motorA, motorB); //パルス幅を元にモーターを回転させる
}
/*
割り込み時に実行される関数
パルス幅を配列「pulse」に記録する
*/
void interruptMotorA() {
if (digitalRead(chA) == HIGH) pulseStart[0] = micros(); //パルスの開始時間を記録
else if (digitalRead(chA) == LOW) pulse[0] = micros() - pulseStart[0]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptMotorB() {
if (digitalRead(chC) == HIGH) pulseStart[1] = micros(); //パルスの開始時間を記録
else if (digitalRead(chC) == LOW) pulse[1] = micros() - pulseStart[1]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
/*
モーターを制御するための関数
引数「motorA」「motorB」に-255~255の値を入れることで制御(マイナスなら逆転)
*/
void motor(int motorA, int motorB) {
//モーターA
if (motorA > 0) {
analogWrite(A_P, motorA); //PWMを変更
//正転
digitalWrite(A_1, HIGH);
digitalWrite(A_2, LOW);
} else if (motorA == 0) {
//ストップ
digitalWrite(A_1, LOW);
digitalWrite(A_2, LOW);
} else {
motorA -= motorA; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(A_P, motorA); //PWMを変更
//逆転
digitalWrite(A_1, LOW);
digitalWrite(A_2, HIGH);
}
//モーターB
if (motorB > 0) {
analogWrite(B_P, motorB); //PWMを変更
//正転
digitalWrite(B_1, HIGH);
digitalWrite(B_2, LOW);
} else if (motorB == 0) {
//ストップ
digitalWrite(B_1, LOW);
digitalWrite(B_2, LOW);
} else {
motorB -= motorB; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(B_P, motorB); //PWMを変更
//逆転
digitalWrite(B_1, LOW);
digitalWrite(B_2, HIGH);
}
}
/*
モーターの回転を止めるための関数
両方のモーターにショートブレーキをかける
*/
void brake() {
digitalWrite(A_1, HIGH);
digitalWrite(A_2, HIGH);
digitalWrite(B_1, HIGH);
digitalWrite(B_2, HIGH);
}
ラジコン型用(割り込み使用, 8チャンネルすべて使用)
brake関数はvoid loop内で使用されていないが、好みでスティックが中央のときにショートブレーキを踏むようにするといいかもしれない。
プロポに存在する8本のチャンネルすべてを使用するプログラムだ。正直CH-A,C以外はミニ相撲ロボットにとってムダだと思うが、せっかくあるのに使わないのはもったいないとも思えてきたため、制作。
これの割り込み不使用版は応答性が悪くなることを想定し未制作。できないこともないが、実感できてしまうほどの遅延(15ms * 8 = 120ms ?)が発生するだろう。
/*
谷口 左千夫によるミニ相撲ロボットラジコン型用プログラム
8本のチャンネルを接続した場合
ライセンス:CC0
*/
//ピンの指定
//近藤科学製プロポ「MC-8」のレシーバー このプログラムではCH-A~Hの8本すべてのチャンネルを使用する
//左スティック
const int chA = 0; //上下
const int chB = 1; //左右
const int chE = 4; //押し込み
//右スティック
const int chC = 2; //上下
const int chD = 3; //左右
const int chF = 5; //押し込み
//左右10段階のスイッチ
const int chG = 6; //左
const int chH = 7; //右
//モータードライバ:TB6612モータードライバー ブレークアウトボードキット 秋月電子通商
//モーターA
const int A_1 = 11;
const int A_2 = 12;
const int A_P = 13;
//モーターB
const int B_1 = 10;
const int B_2 = 9;
const int B_P = 8;
/*
パルス幅の仕様
CH-A~D:890-1496-2100(μs)
890,1496,2100がそれぞれ 一番下,真ん中,一番上となる
真ん中できれいに1496μsのパルス幅を導き出すのは難しい
誤差を考慮しこのプログラムでは890-1476~1516-2100とする
A,Cはモーター制御用に
B,Dは未割り当て
CH-E/F:1496-1995(μs)
スティックの押し込み
誤差を考慮しこのプログラムでは1471~1521-1970~2020とする
モーターを100%の回転数で正転/逆転させる
CH-G/H:997-1496-1995(μs)
10段階のコントロール
*/
volatile unsigned long pulseStart[30]; //パルスの開始時間を記録 インデックスにピン番号が対応する
volatile unsigned long pulse[30]; //パルス幅を記録 インデックスにピン番号が対応する
void setup() {
for (int i = 0; i < 8; i++) pinMode(i, INPUT); //GP0からGP7の8本のピンはすべて入力に設定
//割り込み
attachInterrupt(chA, interruptChA, CHANGE);
attachInterrupt(chB, interruptChB, CHANGE);
attachInterrupt(chC, interruptChC, CHANGE);
attachInterrupt(chD, interruptChD, CHANGE);
attachInterrupt(chE, interruptChE, CHANGE);
attachInterrupt(chF, interruptChF, CHANGE);
attachInterrupt(chG, interruptChG, CHANGE);
attachInterrupt(chH, interruptChH, CHANGE);
}
int motorA, motorB; //-255~255に変換されたパルス幅を記録
void loop() {
//chAのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[chA] > 1516) {
motorA = map(pulse[chA], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[chA] < 1476) {
motorA = map(pulse[chA], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorA = 0;
//chCのパルス幅を-255~255に変換
//スティックが真ん中よりも上のとき
if (pulse[chC] > 1516) {
motorB = map(pulse[chC], 1496, 2100, 0, 255);
}
//スティックが真ん中よりも下のとき
else if (pulse[chC] < 1476) {
motorB = map(pulse[chC], 890, 1496, 255, 0);
}
//スティックが真ん中のとき
else
motorB = 0;
//スティックを押し込んだとき
//左スティックの押し込みなら正転
if (1471 <= chE && 1521 <= chE) {
motorA = 255;
motorB = 255;
}
//右スティックの押し込みなら逆転
if (1970 <= chF && 2020 <= chF) {
motorB = -255;
motorB = -255;
}
//10段階のコントロール
//左
switch (map(chG, 997, 1496, 0, 9)) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
case 9:
break;
}
switch (map(chG, 1496, 1995, 0, 9)) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
case 9:
break;
}
//右
switch (map(chH, 997, 1496, 0, 9)) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
case 9:
break;
}
switch (map(chH, 1496, 1995, 0, 9)) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
case 9:
break;
}
motor(motorA, motorB); //パルス幅を元にモーターを回転させる
}
/*
割り込み時に実行される関数
パルス幅を配列「pulse」に記録する
*/
void interruptChA() {
if (digitalRead(chA) == HIGH) pulseStart[chA] = micros(); //パルスの開始時間を記録
else if (digitalRead(chA) == LOW) pulse[chA] = micros() - pulseStart[chA]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChB() {
if (digitalRead(chB) == HIGH) pulseStart[chB] = micros(); //パルスの開始時間を記録
else if (digitalRead(chB) == LOW) pulse[chB] = micros() - pulseStart[chB]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChC() {
if (digitalRead(chC) == HIGH) pulseStart[chC] = micros(); //パルスの開始時間を記録
else if (digitalRead(chC) == LOW) pulse[chC] = micros() - pulseStart[chC]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChD() {
if (digitalRead(chD) == HIGH) pulseStart[chD] = micros(); //パルスの開始時間を記録
else if (digitalRead(chD) == LOW) pulse[chD] = micros() - pulseStart[chD]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChE() {
if (digitalRead(chE) == HIGH) pulseStart[chE] = micros(); //パルスの開始時間を記録
else if (digitalRead(chE) == LOW) pulse[chE] = micros() - pulseStart[chE]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChF() {
if (digitalRead(chF) == HIGH) pulseStart[chF] = micros(); //パルスの開始時間を記録
else if (digitalRead(chF) == LOW) pulse[chF] = micros() - pulseStart[chF]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChG() {
if (digitalRead(chG) == HIGH) pulseStart[chG] = micros(); //パルスの開始時間を記録
else if (digitalRead(chG) == LOW) pulse[chG] = micros() - pulseStart[chG]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
void interruptChH() {
if (digitalRead(chH) == HIGH) pulseStart[chH] = micros(); //パルスの開始時間を記録
else if (digitalRead(chH) == LOW) pulse[chH] = micros() - pulseStart[chH]; //終了時間(現在の時間)からパルスの開始時間を減算することでパルス幅を求めて記録
}
/*
モーターを制御するための関数
引数「motorA」「motorB」に-255~255の値を入れることで制御(マイナスなら逆転)
*/
void motor(int motorA, int motorB) {
//モーターA
if (motorA > 0) {
analogWrite(A_P, motorA); //PWMを変更
//正転
digitalWrite(A_1, HIGH);
digitalWrite(A_2, LOW);
} else if (motorA == 0) {
//ストップ
digitalWrite(A_1, LOW);
digitalWrite(A_2, LOW);
} else {
motorA -= motorA; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(A_P, motorA); //PWMを変更
//逆転
digitalWrite(A_1, LOW);
digitalWrite(A_2, HIGH);
}
//モーターB
if (motorB > 0) {
analogWrite(B_P, motorB); //PWMを変更
//正転
digitalWrite(B_1, HIGH);
digitalWrite(B_2, LOW);
} else if (motorB == 0) {
//ストップ
digitalWrite(B_1, LOW);
digitalWrite(B_2, LOW);
} else {
motorB -= motorB; //0以下の数字であるためマイナスをかけ絶対値にする
analogWrite(B_P, motorB); //PWMを変更
//逆転
digitalWrite(B_1, LOW);
digitalWrite(B_2, HIGH);
}
}
/*
モーターの回転を止めるための関数
両方のモーターにショートブレーキをかける
*/
void brake() {
digitalWrite(A_1, HIGH);
digitalWrite(A_2, HIGH);
digitalWrite(B_1, HIGH);
digitalWrite(B_2, HIGH);
}
終わりに
ラジコン型用のプログラムはいくつか載せたが、その中でも応答性に富んだ割り込み使用のものを推奨する。あくまでもマイコンボードの問題でそれができない場合にのみ割り込み不使用のものを選ぶとよいだろう。
基板のデータについては未完成。あくまでも妄想にすぎない。機械的な部分については別の記事として書くかもしれない。