春休みになり、ようやく授業の制約を解かれてマウスに勤しむようになったのは良いものの、作った機体(笑)の足回りがクソすぎて全く制御できず、部室で一人絶望する日々を送っています。

 

あまりにうまくいかないのと、もう老害なのにまともなマウスを作ったことがなく、「このままでは2年間毎月部費払ってたマンになってしまうのではないか」という懸念の声(自分)があり、半分ヤケクソ気味に新しい機体の設計を行なっています。

 

まあ、そんなことはどうでもよくて、今回はFusion360でCADったPCBの基板をdxf形式にエクスポートし、Kicadにインポートするのに問題があったのでその解決法を備忘録的に書いておこうと思います。

 

この記事に書いてあるのとほとんど同じです。仲介にblenderでもうまくいったよって話です。

 

まず、そのままfusion360のデータからdxf形式でエクスポートを行う方法を書いておく、これで問題ないのであればそれでいいし。

 

作成したモデルを選択し、その面で右クリックして「スケッチを作成」を選択する。

 

 

そうすると、ヒエラルキーの中に新たなスケッチができているので、「DFX形式で保存」を選択、テキトーな名前でデスクトップにでも保存すればOK。

 

 

で、kicad起動してPCBで「File」の「import」の「DXF File」を選択すると以下が表示される。

 

 

ついでに、Autodesk Viewで同じデータを見てみる。

 

 

なんか君、形変わってない?

 

ってなる。これが問題。

どうもフィレットの曲線が無視されてしまうらしく、カクカクしたものが出てきてしまう。原因は不明。

 

ここで、Blenderを使ってこれを解決する方法がある。前準備としてFusion360でボディをSTL形式で保存しておく。

 

 

まず、Blenderを起動し、謎の立方体をxで消去し、File → import → STLでstl形式のデータをインポート。

 

 

おそらくこのように意味不明な角度と位置で現れるので、うまい具合に調整する。

 

面にする必要があるので、Edit modeでbコマンドで矩形選択し、上半分をxで消去。(Vertices)

 

 

このままでは図形内部の線も入ってしまうので、消去する。

 

 

真ん中下のバーでクリック内容を辺にするように変更する。外側の線の上でalt+右クリックで外周を選択し、ctrl+iで選択反転して内部の線のみを選択できる。xキーでOnly Edges & Facesを選択して完成。

 

 

dxf形式のデータにエクスクルードするにはBlenderではアドオンが必要である。File → User preferanceでdxf exportにチェック入れて保存すると、アドオンが適応される。

 

File → export → dxf で保存。左下にパラメータが入っているが、画像のように変えるとうまくいくはず。

 

 

後はkicadでインポートして完成!

 

 

いかがだったでしょうか。最後がすこし駆け足気味になってしまいました。自分はコミュ力の問題で、まともに先輩に質問もできないんで、せめてブログでは懇切丁寧に説明しようとおもって頑張ってみました。

 

この記事が誰かの役に立つことを願ってます。まあ、Blenderをパソコンにインストールしている組み込みエンジニアがいるのかなぞだけど。

 

ではまた。

 前回前々回に続き、ターミナル上で迷路情報を表示させる。

 

 前回のところでは、矢印で示された経路は確かに最短経路ではあるが、それがもっとも優れた経路とは言えない。同じ最短経路でも曲がり角の少ない方が加速しやすく、優れたルートである。また、発展した機体であれば、斜め走行というジグザグの道を斜めに直進するテクニックも持っている。このような複雑な条件を持つ経路の中で、もっとも優れた経路を出すには、全ての経路での機体の動きを記録し、比較するしかない。

 前回のスクリプトからかなり苦労したのだが、今回なんとかこれを作ることができた。まず、一つの経路を一旦記録してから、別の経路で進むような記録方法は、矢印表記のために部屋のマップ数をいじってるため、現実的ではない。ここで考えたのは、例えば機体が上にも右にも移動できるときは、まず右に機体だけを持っていき、上にも分身した新たな機体が出現するというものである。新たに生成した機体には分身元の経路データを丸写しすることで、まるで最初からここまで進んできたかのような感じで経路探索を続けることができる。このようにして分身が分身を作るようにして経路数の数だけ機体が分身し、各経路にルートが記録される仕組みである。

 ここに行き着くまでにかなりの苦労があった。まず、すでに通った部屋には矢印を表示したいがために部屋のマップ数を変更したせいで、他の機体がそれを避けて通ろうとしてあらぬ方向へ行ってしまうという問題が発生した。これを解決すると、今度は分身が分身元の経路にもう一度分身を行うという無限ループが発生し、エラーの嵐が発生したのである。

 

 

 それをなんとか押さえつけ、エラーを潰していくことでコードが完成した。

 コードの経路では、複数のルートが2次元リストに記録され、その中でももっとも曲がり角の少ない経路を選ぶようにさせた。

 

 

 

 

これらが完成した経路表示である。一見するとカオスに見えるが、画像上部にある"There was ○○ way to go."というのが実際に存在する最短経路の数を表している。

 ここでは最も経路数の少ない2枚目の迷路を見ていこうと思う。この経路は途中までは一本道であるが、ゴール寸前で縦2×横3マスの箇所で機体が分裂し3通りの経路が存在していることがわかる。

 

 このようにして複数の最短経路を持つ迷路の中で最も優れた経路を選び出すことができた。

前回の記事で作成したターミナル上の迷路をさらに改良して、足立法で、スタートからゴールまでの最短経路を表示させたい。

 

まず、前回の記事の迷路の壁の数字はただの座標であり、本来は探索中に見つけた情報を格納する配列から値を代入するのが正しい。マイクロマウスの公式試合ではクラシックでは16×16の迷路を探索するのであり、そこを変更する必要はないのだが、せっかくなのでプログラム実行時の引数で迷路の大きさを操作できるようにしたい。

 

さらに壁の情報だけではなく、区画の情報も必要だ。足立法ではマッピングを行うことで最短経路を得られるので、その情報を得る必要がある。

 

まずは、壁の情報から。引数で変動するような配列の情報は動的配列であるstd::vectorを使うといい。2次元配列なので、宣言は

 

std::vector< std::vector<int> > kabe;

 

みたいな感じで行う。これに、

 

kabe.resize(16);

for (int i; i < 16; i++){

    kabe[i].resize(16);

}

 

とすると、16×16の2次元配列が出来上がる。今回はシミュレータもかねてこの配列に0か1をランダムで入れる。私はたての壁も横の壁も取得したいので、3次元のベクトルをしようしている。

 

 

 

 

次に足立法について。まず、全ての部屋の数字を255にしてゴール近くの部屋の番号を0にし、その0から一つ増えた1を四方に拡散するように広げる。次に1の周りに2を置く・・・というように機体のいる座標まで繰り返し、最短経路を見つけ出すことができる。今回は壁の有無はあくまでランダムなので、ゴールすることができないことを考慮し、拡散ができないようになるとループを抜けるようにした。

 

 

そこから、現在の座標からゴールまで経路を辿ることで、最短のルートを行くことができる。ちょっと工夫して経路を示すように表示してみる。

 

 

面倒なので、コードをそのまま貼る。

 

//meiro.cpp

 

//g++ meiro.cpp -o meiro

//./meiro 16 16

 

#include <stdio.h>

#include <stdlib.h>

#include <iostream>

#include <vector>

#include <time.h>

#include <algorithm>

 

int row;

int col;

int road;

std::vector<int> car;

std::vector< std::vector<int> > heya;

std::vector< std::vector<int> >::iterator heya_num = heya.begin();

std::vector< std::vector< std::vector<int> > > kabe;

 

void kabe_rand(){

int r;

int c;

srand(time(NULL));

for (r=0; r<row; r++){

for (c=0; c<col+1; c++){

if (rand()%10>2){

kabe[0][r][c]=10;

}else{

kabe[0][r][c]=11;

}

}

}

for (r=0; r<row+1; r++){

for (c=0; c<col; c++){

if (rand()%10>3){

kabe[1][r][c]=10;

}else{

kabe[1][r][c]=11;

}

}

}

}

 

void kabe_input(int m, int r, int c){

if(kabe[m][r][c] < 10){

std::cout << kabe[m][r][c];

return;

}else if(m == 0){

if (kabe[m][r][c] == 11){

std::cout << "━━";

}else if (kabe[m][r][c] == 10){

std::cout << " ";

}

return;

}else if(m == 1){

if (kabe[m][r][c] == 11){

std::cout << "┃ ";

}else if (kabe[m][r][c] == 10){

std::cout << " ";

}

return;

}

}

 

void heya_input(int r, int c){

if(heya[r][c] < 256){

if (heya[r][c] < 100){

if (heya[r][c] < 10){

std::cout << " " << heya[r][c] << " ";

}else{

std::cout << " " << heya[r][c] << " ";

}

}else{

std::cout << heya[r][c] << " ";

}

}else if(heya[r][c] == 256){

std::cout << " ↑ ";

}else if(heya[r][c] == 257){

std::cout << "→→→ ";

}else if(heya[r][c] == 258){

std::cout << " ↓ ";

}else if(heya[r][c] == 259){

std::cout << "←←← ";

}else if(heya[r][c] == 260){

std::cout << " ^ ";

}else if(heya[r][c] == 261){

std::cout << " > ";

}else if(heya[r][c] == 262){

std::cout << " v ";

}else if(heya[r][c] == 263){

std::cout << " < ";

}else if(heya[r][c] == 264){

std::cout << " ┏━ ";

}else if(heya[r][c] == 265){

std::cout << "━┓ ";

}else if(heya[r][c] == 266){

std::cout << " ┗━ ";

}else if(heya[r][c] == 267){

std::cout << "━┛ ";

}

return;

}


 

void running(){

int length = heya[car[0]][car[1]];

int direction = car[2];

int i;

 

for (i=0; i<length; i++){

if (car[0] + 1 < row){

if (heya[car[0] + 1][car[1]] < length - i && kabe[1][car[0] + 1][car[1]] == 10){

//部屋の進行 >

if (heya[car[0]][car[1]] == 261){//>

heya[car[0]][car[1]] = 257;//→

}else if (heya[car[0]][car[1]] == 262){//v

heya[car[0]][car[1]] = 266;//┗━

}else if (heya[car[0]][car[1]] == 260){//^

heya[car[0]][car[1]] = 264;//┏━

}

car[0] += 1;

heya[car[0]][car[1]] = 261;

continue;

}

}

if (car[0] - 1 > -1){

if (heya[car[0] - 1][car[1]] < length - i && kabe[1][car[0]][car[1]] == 10){

//部屋の進行 <

if (heya[car[0]][car[1]] == 263){//<

heya[car[0]][car[1]] = 259;

}else if (heya[car[0]][car[1]] == 262){//v

heya[car[0]][car[1]] = 267;

}else if (heya[car[0]][car[1]] == 260){//^

heya[car[0]][car[1]] = 265;

}

car[0] -= 1;

heya[car[0]][car[1]] = 263;

continue;

}

}

if (car[1] + 1 < col){

if (heya[car[0]][car[1] + 1] < length - i && kabe[0][car[0]][car[1] + 1] == 10){

//部屋の進行 v

if (heya[car[0]][car[1]] == 262){//v

heya[car[0]][car[1]] = 258;//

}else if (heya[car[0]][car[1]] == 261){//>

heya[car[0]][car[1]] = 265;//

}else if (heya[car[0]][car[1]] == 263){//<

heya[car[0]][car[1]] = 264;//

}

car[1] += 1;

heya[car[0]][car[1]] = 262;

continue;

}

}

if (car[1] - 1 > -1){

if (heya[car[0]][car[1] - 1] < length - i && kabe[0][car[0]][car[1]] == 10){

//部屋の進行 ^

if (heya[car[0]][car[1]] == 260){//^

heya[car[0]][car[1]] = 256;//↑

}else if (heya[car[0]][car[1]] == 261){//>

heya[car[0]][car[1]] = 267;//━┛

}else if (heya[car[0]][car[1]] == 263){//<

heya[car[0]][car[1]] = 266;//┗━

}

car[1] -= 1;

heya[car[0]][car[1]] = 260;

continue;

}

}

std::cout << "something is wrong : " << i << std::endl;

}

 

return;

}

 

void map(){

std::vector< std::vector<int> > pos;

std::vector< std::vector<int> > matrix;

int map_num;

int r;

int c;

int i;

 

for (;;){

pos.resize(2);

map_num = 0;

for(map_num = 0; map_num<256; map_num++){

pos.clear();

pos.resize(2);

for(c=0; c<col; c++){

for(r=0; r<row; r++){

if (heya[r][c] == map_num){

pos[0].push_back(r);

pos[1].push_back(c);

}

}

}

if (pos[0].size() == pos[1].size()){

/*for(i=0; i<pos[0].size(); i++){

std::cout << pos[0][i] << "," << pos[1][i] << std::endl;

}*/

}

if (pos[0].size()==0){

//std::cout << map_num << std::endl;

if (map_num > road){

road = map_num;

}else{

std::cout << "mapping is done" << std::endl;

return;

}

break;

}else{

matrix.clear();

std::copy(pos.begin(), pos.end(), std::back_inserter(matrix));

}

}

/*

for(i=0; i<matrix[0].size(); i++){

std::cout << matrix[0][i] << "," << matrix[1][i] << std::endl;

}*/

 

for(i=0; i<matrix[0].size(); i++){

if (matrix[0][i]+1 < row){

if (heya[matrix[0][i] + 1][matrix[1][i]] > map_num && kabe[1][matrix[0][i] + 1][matrix[1][i]] == 10){

heya[matrix[0][i] + 1][matrix[1][i]] = map_num;

if (matrix[0][i] + 1 == car[0] && matrix[1][i] == car[1]){

std::cout << "the root has discovered. the length : " << map_num << std::endl;

running();

return;

}

}

}

if (matrix[0][i]-1 > -1){

if (heya[matrix[0][i] - 1][matrix[1][i]] > map_num && kabe[1][matrix[0][i]][matrix[1][i]] == 10){

heya[matrix[0][i] - 1][matrix[1][i]] = map_num;

if (matrix[0][i] - 1 == car[0] && matrix[1][i] == car[1]){

std::cout << "the root has discovered. the length : " << map_num << std::endl;

running();

return;

}

}

}

if (matrix[1][i]+1 < col){

if (heya[matrix[0][i]][matrix[1][i] + 1] > map_num && kabe[0][matrix[0][i]][matrix[1][i] + 1] == 10){

heya[matrix[0][i]][matrix[1][i] + 1] = map_num;

if (matrix[0][i] == car[0] && matrix[1][i] + 1 == car[1]){

std::cout << "the root has discovered. the length : " << map_num << std::endl;

running();

return;

}

}

}

if (matrix[1][i]-1 > -1){

if (heya[matrix[0][i]][matrix[1][i] - 1] > map_num && kabe[0][matrix[0][i]][matrix[1][i]] == 10){

heya[matrix[0][i]][matrix[1][i] - 1] = map_num;

if (matrix[0][i] == car[0] && matrix[1][i] - 1 == car[1]){

std::cout << "the root has discovered. the length : " << map_num << std::endl;

running();

return;

}

}

}

}

}

 

return;

}

 

void meiro(){

int r;

int c;

 

std::cout << "┏━━";

kabe_input(0,0,0);

std::cout << "━━";

for (r=1; r<row; r++){

std::cout << "┳━━";

kabe_input(0,r,0);

std::cout << "━━";

}

printf("┓\n");

 

kabe_input(1,0,0);

std::cout << " ";

heya_input(0,0);

for (r=1; r<row; r++){

kabe_input(1,r,0);

std::cout << " ";

heya_input(r,0);

}

kabe_input(1,row,0);

std::cout << std::endl;

 

for (c=1; c<col; c++){

std::cout << "┣━━";

kabe_input(0,0,c);

std::cout << "━━";

for (r=1; r<row; r++){

std::cout << "╋━━";

kabe_input(0,r,c);

std::cout << "━━";

}

std::cout << "┫" << std::endl;

 

kabe_input(1,0,c);

std::cout << " ";

heya_input(0,c);

for (r=1; r<row; r++){

kabe_input(1,r,c);

std::cout << " ";

heya_input(r,c);

}

kabe_input(1,row,c);

std::cout << std::endl;

}

 

std::cout << "┗━━";

kabe_input(0,0,col);

std::cout << "━━";

for (r=1; r<row; r++){

std::cout << "┻━━";

kabe_input(0,r,col);

std::cout << "━━";

}

printf("┛\n");

}

 

int main(int argc, char* argv[]){

if (argc != 3){

printf("error : number is %d\n",argc);

return 1;

}

 

row = atoi(argv[1]);

col = atoi(argv[2]);

 

if (row < 1 || col < 1){

printf("error : number must be more than zero.\n");

return 1;

 

}

 

int i;

for(i=1; i<argc; i++){

printf("number %d is %d \n",i,atoi(argv[i]));

}

 

int r;

int c;


 

kabe.resize(2);

kabe[0].resize(row);

for (r=0; r<row; r++){

kabe[0][r].resize(col+1);

for (c=0; c<col+1; c++){

kabe[0][r][c] = 0;

}

}

kabe[1].resize(row+1);

for (r=0; r<row+1; r++){

kabe[1][r].resize(col);

for (c=0; c<col; c++){

kabe[1][r][c] = 0;

}

}

 

heya.resize(row);

for (r=0; r<row; r++){

heya[r].resize(col);

for (c=0; c<col; c++){

heya[r][c] = 255;

}

}

 

car.resize(3);

car[0] = 0;

car[1] = col - 1;

car[2] = 260;

 

heya[car[0]][car[1]] = car[2];

heya[7][7]=0;

heya[7][8]=0;

heya[8][7]=0;

heya[8][8]=0;

 

kabe_rand();

map();

meiro();

}

 

 

このコードを貼ってg++ meiro.cpp -o meiroと打って./meiro 16 16と実行すると、迷路が表示される。

 

 

 

 

 

 

 

 

 

 

もう少し改良の余地があるかもしれない。

ちょっと小休憩として、ターミナル上で迷路の観測状況を表示できるようにC++でスクリプトを組んでみよう。

 

この計画はマウスが経路探索中にどこを理解し、どこが未観測かを知るフィードバックが必要だと考えて作ることにした。

openCVでウィンドウを開いてそれっぽいものも作れるだろうが、もっと簡易で作りたい。

 

//meiro.cpp

#include <stdio.h>

#include <stdlib.h>

 

void meiro(int row, int col){

int r;

int c;

 

printf("┏━0━");

for (r=1; r<row; r++){

printf("┳━0━");

}

printf("┓\n");

 

printf("0 ");

for (r=1; r<row; r++){

printf("%d ",r);

}

printf("%d\n",row);

 

for (c=1; c<col; c++){

printf("┣━%d━",c);

for (r=1; r<row; r++){

printf("╋━%d━",c);

}

printf("┫\n");

 

printf("0 ");

for (r=1; r<row; r++){

printf("%d ",r);

}

printf("%d\n",row);

}

 

printf("┗━%d━",col);

for (r=1; r<row; r++){

printf("┻━%d━",col);

}

printf("┛\n");

}

 

int main(int argc, char* argv[]){

if (argc != 3){

printf("error : number is %d\n",argc);

return 1;

}

 

if (atoi(argv[1]) < 1 || atoi(argv[2]) < 1){

printf("error : number must be more than zero.\n");

return 1;

 

}

 

int i;

for(i=1; i<argc; i++){

printf("number %d is %d \n",i,atoi(argv[i]));

}

meiro(atoi(argv[1]), atoi(argv[2]));

}

 

ターミナルを開いて

g++ meiro.cpp -o mei

 

と打つと、ディレクトリ上にmeiという名の実行ファイルできているはずだ。

./mei 5 5

と打つと

 

 

といった感じで迷路の各壁にその座標がでる。

2桁の数字が入ると行がずれて悲惨なことになるが、実際には壁の有無(0.0 ~ 1.0)で少数点一桁しか表示するつもりがないのでOKだ(?)。

 

次回はその確率の配列を代入する。

前に今使っているマイクロマウスの機体を公開したが、見てわかるように色々とごちゃごちゃしている。

 

 

とにかくケーブルが鬱陶しい。まずRaspberry PiとArduinoのシリアル通信ようにUSBケーブル、ラズパイとwebカメラのUSBケーブル、サーボモーターの3色ケーブルに、LiPo電池のケーブルなど様々なものがある。

特に既製品であるUSBのケーブルが長すぎる。画像では見えないが、実は後ろの方でぐるぐる巻きにして結んである。全然スマートじゃない。

 

よって、このケーブルたちを加工する作業を行なった。

まず、webカメラから。ニッパーでケーブルを切断すると4色のケーブルが出てくる。

 

それぞれがGND(黒),5V(赤),TX(緑),RX(白)である。これをパーツ店で買ってきたUSB端子に半田付けする。

 

 

これで正常に動くはず。

 

 

できた!

カメラのサーボモーターによる駆動には、回転を支えるスタンドが必要だったので、3Dプリンターで出力しました。

 

Autodesk Fusion 360でCADで設計し、xyz ware を使って、da Vinch 1.0 Aio から出力。

xyz ware はアカウント登録すれば無料でMac・ Windowsでも使えるのでおすすめです。

 

 

これがFusion上での設計。黒いのがカメラ、青いのはサーボモータ。

これを各個体でSTL形式で出力し、xyz wareで読み込む。

 

 

中央の画像が3Dプリンター上で出来上がる配置。青い枠が3Dプリンターのドアの方向。

3Dプリンターは上からアクリルをノズルで溶かして積み重ねる(スライスする)ので、その方向は脆いという欠点がある。力がかかる方向にスライスの方向が被らないようにしよう。

 

左側のアイコンでオブジェクトの配置を変更することができる。

 

右側のアイコンはプリンターとのインターフェイスを設定する。

上のアイコンはusbで挿したポートの指定を行い、下のアイコンで印刷中の情報を取得できる。

(ただし、非同期ぽいので、本体の画面をみた方が100倍信頼性が高い)

 

上のバーで印刷のアイコンを押すと、様々な指定ができる。

 

 

ラフトとはオブジェクトを作る前に敷くシートのようなもので、これがあると安定して印刷できる。

サポートは空中に出てくる突起とかを印刷するときに、倒れないように支える部分のこと。

この二つはチェックしておいた方がいい。私はそれ以外はデフォルトのままだ。

 

 

出力結果は以下のようになった。下のウネウネがラフト、穴の中や突起の下にあるのがサポートである。

ラフトとサポートは手で取れる。ヤスリで滑らかにするとなお良い。

 

こんな感じです。

 

 

・駆動部分はDCモーターで、TA7291Pで制御

・センサーは一番上のwebカメラのみ

・マイコンはラズパイとArduino Unoで、カメラの画像処理とモーター制御をおこなう。

・カメラ横にサーボモーターで角度制御

・電池はArduino側はLiPo電池、ラズパイ側は市販のモバイルバッテリー

主にマイクロマウスのことを記事に書いていきます。

もう本体は出来上がっているのですが、備忘録的にいろいろ書き込んでいく予定です。

 

よろしくお願いします。