マウスのハードウェア修正

卒論進捗ヤバ太郎

コロナヤバいですね。毎日過去最多更新していますね。おかげさまで大学の研究室入れなくなり、自宅で卒論を書くことを余儀なくされております。自宅だと集中できないタイプで、無理やり研究室行って進捗出してた自分にとって結構つらいです。 昨日なんかこのロボ太さんのツイートの4枚目みたいになってました。卒論やだーって床と平行になってタコピーの原罪とか見てクネクネしてたら、外暗くなってて、タコピーの話の絶望感と自分の不甲斐なさによる絶望感が合算され、メンタルが地獄でした。

今日は朝起きて、さすがに卒論書かんとヤバいよなーと思い卒論書き始めましたが、マウスもやらないとヤバいよなーって思ってしまい、どっちつかずになって、集中できませんでした。卒論よりマウスのモチベのほうが高いので、今日はマウスをやることにしました。 前回の振り返りの記事で、センサ周りの設計し直して~と新規にハードから設計し直す匂わせをしていますが、卒研とかGCI2021とかやってる時点でハードから設計し直して3月に間に合うわけがないです...はい...Cheeseの先輩方と相談した結果、既存のハードの修正のみにとどめることにしました。 tosonshirley.hatenablog.com 今のハードの問題点、数えればキリがないのですが、走行に支障が出やすい且つ、修正可能なものを書き出した結果、2つ出てきました。今回はそれを修正しました。

壁センサの位置修正

1つめは、壁センサの位置です。横壁センサの発光部と受光部の間隔が離れています。ハード設計時、センサの発光部と受光部の間隔とか大して気にも留めなかったのですが、実際に組み立てて値を取ってみると、問題が出てきました。 f:id:tosonshirley:20220129000414j:plain 使っているセンサがSFH4550(熱圧縮チューブ付き)+ST1KL3Aと、発光部受光部共に指向性が高い組み合わせであるからかもしれませんが、センサの間隔をあけてしまった結果 以下のような特性になってしまいました。 f:id:tosonshirley:20220129003421j:plain 理想的なのは、赤点線のような特性ですが、センサ平行時、指向性が高い故に、壁に近づけると逆に値が下がってしまう状況でした。そのため、壁に近づけても値が下がらないよう角度をつけて 配置してみましたが、角度をつけすぎると、逆に遠いところでの壁判定ができない(目が悪い)状況でした。とりあえず、若干角度をつけて、赤点線になるべく近づくように調整していましたが、衝撃等でほんの少し角度がずれただけで、特性が大幅に変わってしまい非常に扱いづらい状況でした。 後で調べてみた結果、

matsui-mouse.blogspot.com

まついさんが、非平行センサは信号強度が下がるがらダメと2013年に結論付けてました。 先人の裏付けもありやはり、マウスのセンサは受光部発光部なるべく間隔を開けず、平行に設置することが重要であると分かりました。 修正は、発光部LEDを切断して、はんだと針金をつかっては延長して受光部との間隔を狭くしました。強度が心配でしたが、指で押した結果ぐらつかないのでとりあえず大丈夫かなと思います。調整終わったらホットボンドで固定しようかなと思います。

D-Subコネクタの取り外し

いままでは、秋月のRX220ボードがRS232Cレベル変換IC実装済みなこともあり、マウスのケツにD-Subコネクタをつけて、マイコンに書き込んでました。しかし、このコネクタが出っ張ってまして、壁当て補正がやりづらい、という問題が起きていました。解決のため、まずソケットを取り外し、ジャンプワイヤ経由でFT234Xで書き換えできるようにすることを考えました。 マイコンボードのレベル変換IC剥がして、ウレタン線でつなぐ。D-Subコネクタとピンソケット交換することが理想でしたが、D-Subコネクタ外した際にスルーホール破壊したので、できなくなりました。(電動はんだ吸い取り器ほしい...) 仕方がないので、ジャンプワイヤ(メス)を切って直接マイコンにはんだ付けすることにしました。不格好ですがとりあえず書き込めているので大丈夫でしょう。

f:id:tosonshirley:20220129013625j:plain

まとめ

さっさと卒論終わらせてマウスやりたいです。とか言ってますが、最近卒論の気晴らしにrustlingsとかやってます。普通に楽しいです。Rustで迷路ソルバーとか書いてみたいなぁ...(卒論から逃げるな)

最近(?)作ったwebアプリ

(一応)マイクロマウスをメインでやっているが、周りの友人や、所属しているサークルにはWeb屋が多く、Webのこと多少分かってないと話についていけない部分が多々あった。そのため、Web実際に触らなくてはという思いがあった。また個人的にもWebには技術的興味があったので、2つ程Webアプリを作ってみた。自分の周りはLalavel+Reactをメインで使う人が多かったが、学習にそれほど時間をかけられなかったため、とりあえずReactよりは学習コスト低めと言われるVueで作ってみた。(本当はReactで作ってみたかったが、ReactHooksリリース以降関数コンポーネント?で作るのが主流っぽく、公式のチュートリアルはクラスコンポーネントだったり、当時、関数コンポーネントで開発する体系的な教材が見当たらなかった。今だったらりあクト!とか見ながら作るかも)

QuizApp

https://quizapp-a6850.web.app/

github.com

作る題材を探していたら、友人が、「スプレッドシートで作問したものをそのままクイズにしてくれるサービスない? GoogleFormで作ってもいいんだけど、手打ちがめんどいのよね」と言われた。折角の機会なので作ってみた。最初、spreadsheetをGASでjsonに変換して、Web API公開、Vue側でgetして表示という形をとろうかなと思ったが、spreadsheetの共有urlの一部を書き換え、クエリでcsvを指定すると、直接csvでダウンロードできることを知った。GASでAPI作るよりも使い勝手が良く、実装が楽なので、GASは使わず直接CSVをダウンロードしてVue側でjsonに変換、処理してみた。 一応クイズアプリなので、作問者用のページ(クイズ生成確認、集計)と回答者用のページを作る必要があった。ページのルーティングが必要なのでVue Routerを用いた。開発当初、Vuexも使うかと考えていたため、Router Vuex全部入りのNuxt.jsで開発した。(結局Vuexは使わなかったが) また、回答を集計して表示という機能を追加するには、何かしらのDBが必要だったため、Cloud Firestoreを用いた。

f:id:tosonshirley:20220124112526p:plain
spreadsheetで問題作成
f:id:tosonshirley:20220124112605p:plain
spreadsheetのIDを入力
f:id:tosonshirley:20220124112649p:plain
クイズ生成確認、回答URL、集計URL生成
f:id:tosonshirley:20220124112858p:plain
回答者ページ
f:id:tosonshirley:20220125164551p:plain
集計ページ

20HoursTimer

github.com

20時間タイマー。何かのスキルや技術は20時間あればある程度は把握できる、みたいなことをTedかなにかの記事で読んで、それをサポートするためのアプリを作ってみたくなって作った。私自身、熱しやすく冷めやすいタイプで、色々手出しては途中で辞めがち。20時間くらいは一つのことに集中したいなという思いもあった。 一つのプロジェクト(20時間でやりたいこと)に対し、一つの20時間タイマ、todoリストを紐づける。ユーザ認証をつける。といったことを実現したかったため、バックエンド側の実装も必要だった。当初、Nuxt.js+Django(Django Rest Framework)+ MySQLで作ろうと考えていたが、CORSエラーや認証周りに手こずってしまった。また、学業やマイクロマウスで忙しくなってきてしまったので、バックエンド側はFirebaseに頼ることにした。しかし、中途半端に作ったバックエンドを時間があるときに完成させて移植したかったので、FirebaseSDKを使わず、REST APIを用いた。FireStore NoSQLであるため、入れ子でDBを作る必要があった(コレクションの生成)REST APIだとコレクションの生成の度、APIを叩かなくてはいけなく、ちょっと実装が大変だった。特別な理由がなければSDKを使った方が楽だなと思った。 現在バリデーションとエラーハンドリングが甘かったり、todoリストのソートがおかしかったりするので修正したいが、卒論とマイクロマウスで忙しく、一旦開発を止めている。

f:id:tosonshirley:20220124111903p:plain

f:id:tosonshirley:20220124111918p:plain

マイクロマウスの振り返りと今後について

最近全然ブログ書いてないな... アドベントカレンダー自体初めてです。よろしくお願いします。

はじめに

マイクロマウス Advent Calendar 2021 - Adventar の24日目に記事になります。 前回はこうへいさんの「マイクロマウス(2輪ロボット)の走行時間の近似推定」でした。

rikei-tawamure.com

走行時間を近似的に算出することで走行時間を意識して調節できるとのこと。次のマウスが走るようになったら、念頭に置いて調節してみようかなと思います。勉強になります。 近似式、シミュレータ作るときにも使えそうだなと思いました。

マウス社会人でも続けている人いますが、やはり、仕事やりながらだと大変そうですね... 私も社会人になってもどうにか時間見つけてマウス続けていきたいです。

本当だったら、「学生大会完走したわーい、個人でもマウスはできるよ!みんなマウスやろうよ!」みたいな記事を書きたかったのですが、残念ながら今年も完走せず、3年目フレッシュマンになってしまい滅茶苦茶辛いです。個人で孤独にマウスに向き合うことへの限界を感じてしまいました。

折角の機会なので、いままでの振り返りと今後について書きたいと思います。普段から進捗ブログ書く癖付けた方が良いですね...

振り返り

マイクロマウスとの出会い

高校生の時に、Youtubeでたまたま見かけた、マイクロマウス全日本大会の動画で、爆速で迷路を駆け抜けるマウスを見て、衝撃を受けたことが始まりでした。それ以来、大学入ったらマウスやりたいなーと思っていました。

B1

しかしながら、実際に進学した大学にはロボコンサークルが無く、また、当時、電子工作もプログラミングも全くの未経験だったことから、マイクロマウス始めるにしても、何から始めたらよいかさっぱり分かりませんでした。

ただ、漠然とプログラミングはできないと不味いよなと思い、プログラミングサークルに入り、DxLibでゲーム作ったりしてました。また、当時、音楽ゲームの影響からDTMにハマってしまって、マウスのことは頭の片隅に追いやられていた感じだったと思います。

B2

2年になり例のkindle本を見つけました。

www.amazon.co.jp

マイクロマウスについてハード設計からソフトまで体系的にまとめられている入門書は当時、本書しかなく(自分の知る限りでは)1マイクロマウスを作りたいと思っていた自分にとって、渡りに船でした。

しかしながらこの本で製作するマウスはエンコーダ付きDCモータを使ったマウスであり、全くの初心者にとっては難易度が高く(当時ステッピングもDCも大して難易度変わらんやろって思っていました...)、また製作にもお金がかかるものでした。

半年くらいバイトして、実際に作り始めたのは、twitter見る限りでは9月からだったようです。

B3

コロナが大流行した年になってしまいました。大学もオンラインになり、通学に2時間近くかけているせいで、平日バイト入れるのが厳しく、土日に単発バイトしかできない学生にとって、通学しなくて良いのはメリットでした。

マウスお金がかかるしバイトするかと思い、近場の電気屋でパソコンサポートのバイトを始めたのですが、これが思った以上にブラックでした。2

折角大学がオンラインになって時間があるのに、気力体力ほとんどバイトに吸われて進捗がさほど生めない状態になってしまい、本末転倒だったので、半年でやめました。3

バイト辞めて進捗生み始めてしばらくたった頃、あることに気が付きます。いままで参考にしてきた「苦しんで作るマイクロマウス(前編)」後編が出版されていないのです。「壁センサのAD変換、モータを回すことはできた。その後どうするんだ?」となり色々自分で調べてみた結果、どうやら制御が必要だと分かり、以下のアールティのブログなどを参考にDCモータの2自由度制御を実装しました。 www.rt-shop.jp 半強制的に進捗を生むため、大学の自由研究みたいな講義の課題にマイクロマウスを選んだりしてました。 www.slideshare.net 迷路探索周りは以下の本を参考にしました。 www.amazon.co.jp

気合で何とか4×4の迷路は走るようになり、全日本大会(オンライン)には間に合わせることができたのでとりあえず出場してみました。しかし、ここでマイクロマウスの難しさを思い知ります。自宅の4×4迷路では走っても、長い距離走らせるフルサイズの迷路だと、誤差が蓄積して走行が不安定になったり、環境光が一定ではないため、壁の読み間違えが起きてしまったりして完走できませんでした。

B4

コロナで大会も未定になり、モチベも下がってしまったため、今年の前半は、VRなりWeb開発なり、某VRSNSで出会ったオタクに感化されて自作OSに手出したり、マイクロマウスとは別のことをやっていました。某VRSNSの界隈がなぜかほとんどセキュリティキャンプ経験者で、彼らに勧められて試しに応募したら、選考通ったので、今年の夏はセキュリティキャンプに参加したりしました。セキュリティキャンプでマイクロマウスに興味持ってくれている人もおり、やっぱ最低限在学中に完走だけはさせたいよなと思い、モチベが少しだけ上がりました。

秋に入りtwitterでアールティさんがキットを無料で配布するキャンペーンを行っていることを知ります。

rt-net.jp

マイクロマウスは個人で行っていましたが、所属しているWeb寄りのプログラミングサークルに声を掛けたところ、後輩数人がやりたいと言ったため、また、私自身キットから知見を得るため、応募しました。

壁切れ、前壁補正等の知見も得ることができ、実装できたので、今回は完走するだろうと思い、先週の学生大会走らせましたが惨敗でした。

状況として、頂いたハーフマウスは、長い直線で誤差が蓄積し、指定の距離進まず曲がるとき壁にぶつかりスタック。櫛の安定性改善にばっかり目が向き、長い距離走らせての距離調整を行わなかったのが原因だと思われます。

自作のクラシックは、家でも、会場の試走迷路でも、壁を読み間違えることはなかったのに、本番壁を読み間違え、スタック。去年センサのAD変換のダイナミックレンジが狭すぎて、壁を読み間違えていることが判明したため、今年は抵抗を変えダイナミックレンジを広げましたが、去年と同じような状況になってしまいました。回路にフィルタを何も入れていなく、LEDを点滅させて差分を取っているだけなので外乱に弱いのだろうなと思います。 f:id:tosonshirley:20220123223822j:plain

今後と目標

流石に個人でマイクロマウスに向き合うのは精神的にも辛くなってきたので、学生大会惨敗と同時に東工大のマイクロマウスサークルcheeseの方にインカレで入部いたしました。 今年の全日本大会も、コロナの影響でポイント無くても出られるらしいので、センサ周りの回路設計し直して、出場したいなと思っています。4先ずは完走させたいです...本当に... あと、完走したら「苦しんで作るマイクロマウス(後編)」みたいなものブログか何かに書きたいですね...書いていいよね...

明日はb4rrAcud4さんです。何を書いてくれるのか楽しみです。


  1. DCモータを使ったマウス入門書って今もこれしかない(?)

  2. 詳細は言えないがtwitterで過去に2 3回炎上している某企業

  3. 3か月くらいで違和感を感じ、すぐに辞めたかったが、店長に人いないんだから辞めるなって説得されてズルズル引き延ばしてしまった…夏のインターンも逃したしさっさと辞めればよかったと今も若干後悔している

  4. Q:卒論追い込みの時期ですが大丈夫ですか… A:にゃーん…

30日OS自作入門1-4日目

積んでいた30日OS本に手をつけてみようと思う。 本当は3月入ったらすぐ手をつける予定だったが、VRにドハマりして何も手をつけないまま半月過ぎていた...やばーい...

環境

最近ヤフオクthinkpadを入手しmanjaro20.2.1(Cinnamon)を入れた。Arch系に慣れる良い機会なのでこのマシンをつかってみる。
書籍はwindowsでの実装で、naskや筆者のツールがlinuxではつかえないため、 以下の記事を参考に進めた。

qiita.com

1日目

やるぞやるぞOSやるぞ

仮想マシンエミュレータqemuと、バイナリエディタ、ghexをインストール。

sudo pacman -Syu qemu-arch-extra ghex

手打ちで、1474560バイト打ち込むのは骨が折れるので、まずは、0埋めしたファイルを作る。

python3 -c 'print("\x00"*1474559)' > helloos.img
du -b helloos.img
1474560 helloos.img

1474560バイト書き込まれたことが確認できれば良し。 ghexでhelloos.imgを編集する。

ghex helloos.img

f:id:tosonshirley:20210315214956p:plain

保存して、qemu上で実行。

qemu-system-i386 helloos.img

f:id:tosonshirley:20210315215345p:plain

わーい

次はアセンブリで記述する。 筆者は独自のアセンブラnaskを使用しているが、windowsでしか使えないため、今回はnasmを使用する。 nasmとnaskの違いは以下を参照。

tools/nask - hrb-wiki

バイナリに変換して、実行。

nasm helloos.asm -o helloos.img
qemu-system-i386 helloos.img

さっきと同じくhello,worldと表示された。ok

少し詰まったところ

19             DB  "HELLO-OS       "
20             DB  "FAT12      "

ディスクの名前とフォーマットの名前のメモリ容量?は決まっているため、11バイト、8バイト指定してあげないといけない。つまりディスクの名前が11文字以下の場合、空白で11文字まで満たす必要がある。 DB "HELLO=OS" としたらエラーが出た。

2日目

アセンブリまとめ

特に命令(ニーモニック)について良く知らないため、勉強もかねて備忘録としてまとめておく。

ORG命令

Origin 開始点を意味する。機械語がメモリの何番地から読み込まれるか指定する。 ORG命令は疑似命令といい、実際にマイクロプロセッサが実行する命令ではない。あくまでアセンブラに指示を与えるためのもの。

ORG    0x7c00

JMP命令

c言語のgo to と同じ。指定したラベルへジャンプする。

JMP    entry

JE命令

jump if equel 条件ジャンプ命令の一種。 比較命令の結果が等しければジャンプ。

CMP    AL,0
JE    fin

MOV命令

今回はintel記法で書いている。第一オペランド(a)に第二オペランド(b)を代入する。AT&T記法だと逆になるらしい。

MOV    a,b
MOV    AL,[SI]

[]はメモリを意味する。SIレジスタに934が入っていたら934番地の値をALに代入。

DB命令

ORGと同じく疑似命令。オペランドで指定した数値を1バイトずつデータとしてメモリに格納。文字列もオペランドとして指定可能。

DB    0x43
DB    "HELLO-OS    "

DB 0x43, 0x01 → 01000011 00000001

DW命令

これも疑似命令。オペランドで指定した数値を2バイトずつデータとしてメモリに格納。
DW 0x43, 0x01 → 00000000 01000011 00000000 00000001

DD命令

疑似命令。オペランドで指定した数値を4バイトずつデータとしてメモリに格納。
DD 0x43, 0x01 → 00000000 00000000 00000000 01000011 00000000 00000000 00000000 00000001

INT命令

ソフトウェア割り込み。

HLT命令

CPUを待機状態にさせる命令。

レジスタまとめ

レジスタについてもまとめておく。
レジスタとは・・・CPU内部の記憶装置。主にフリップフロップで構成されており、アクセス速度が速い。~1ns

AX(アキュムレータ)・・・算術演算操作の結果を格納
CX(カウンタ)・・・ループ命令等のカウントを記録
DX(データ)・・・算術演算操作とI/O操作に使用
BX(ベース)・・・算術演算とセグメントモードでのDSのデータ指定
SP(スタックポインタ)・・・スタックのトップを指すポインタ
BP(スタックベースポインタ)・・・スタックのベースを指す
SI(ソースインデックス)・・・ストリーム操作(MOV命令等)のソース(入力元)のポインタ
DI(デスティネーションインデックス)・・・ストリーム操作(MOV命令等)のディスティネーション(出力先)のポインタ
上記は16bitの場合。32bitの場合は頭にEが付く。

メモリマップ

BIOSが確保するメモリ領域や、自由に使ってはいけない領域は決まっている。
例えばブートセクタが読まれるアドレスは0x00007c00 - 0x00007dff である。
以下を参照。
(AT)memorymap - os-wiki

3日目

詰まるところはなかったと思う。
IPL(初期プログラムローダ)の実装。

アセンブリ

JC命令

jump if carry キャリーフラグ(EEFLAGSレジスタの一種。1bitのみのレジスタ。)が1ならジャンプ。

JC    error

JNC命令

jump if not carry キャリーフラグが0ならジャンプ。

JBE命令

jump if below or equal 小さいかもしくは等しければジャンプ。

CMP    CL,18
JBE    readloop  ; CL <= 18ならreadloopへ

4日目

ここからアセンブリを抜け出して、C言語で書いていく。 冒頭の記事を参考にリンカを作成したが、コンパイルgccが.note.gnu.propertyという注釈セクションを吐き出してしまい、これが.dataセクション(初期値をもつ変数を格納するためのセクション)とメモリ領域がかぶってしまい、エラーが出る。
f:id:tosonshirley:20210421091903p:plain

注釈セクションは他のプログラムから準拠性や互換性などを確認するためのものであり、今回は必要ないので、リンカスクリプトで指定して、生成されないようにした。

SECTIONS
{
    .head 0x0 : {
        LONG(64 * 1024)  /*  0 : stack+.data+heap の大きさ(4KBの倍数) */
        LONG(0x69726148)      /*  4 : シグネチャ "Hari" */
        LONG(0)               /*  8 : mmarea の大きさ(4KBの倍数) */
        LONG(0x310000)        /* 12 : スタック初期値&.data転送先 */
        LONG(SIZEOF(.data))   /* 16 : .dataサイズ */
        LONG(LOADADDR(.data)) /* 20 : .dataの初期値列のファイル位置 */
        LONG(0xE9000000)      /* 24 : 0xE9000000 */
        LONG(HariMain - 0x20) /* 28 : エントリアドレス - 0x20 */
        LONG(0)               /* 32 : heap領域(malloc領域)開始アドレス */
    }

    .text : { *(.text) }

    .data 0x310000 : AT ( ADDR(.text) + SIZEOF(.text) ) {
        *(.data)
        *(.rodata*)
        *(.bss)
    }

    /DISCARD/ : { *(.eh_frame) *(.note.gnu.property) }

}

他詰んだところはなかったと思う。 一日で一日のやることが終わらないことが多く、意外と時間がかかる...完成には2ヶ月くらいかかるかな...

UnityとKinectで作る体験型ジェネラティブアートみたいなもの

今更感あるけど、 今年の学祭(去年になっちゃったね)で展示したものの紹介。

サークルの備品でほこりかぶってたKinectをどうにか活用できないかなと思って、手を付けたが、初代Kinect(v1)であったため情報が少なく、レポート等で忙しく(言い訳)、結局2週間程度で無理やり形に持って行った。完成度は高いとはあまり言えないのであしからず。

最初ゲームを作ろうと思ったが、なぜか当たり判定が死んで、半日程度格闘したが原因がわからなかったので別のものを作ろうと考えた。サークルの出し物がデザイン寄りなのも考えて、ジェネラティブアート的なのを作ろうと思った。

「Unity ジェネラティブアート」でググると一番上にこのサイトが出てくる。

ics.media

これをKinectで制御したらまあまあ面白いものができるのではと思った。
描写に関しては上のサイトとほとんど同じ...だとつまらないので、いくつかモードを追加した。

Kinectの前に人がいないとき、キャンパスに説明などを描写する待機モード。 f:id:tosonshirley:20200101140545p:plain

上のサイトをもとに作った、描写モード1。 f:id:tosonshirley:20200101141614p:plain f:id:tosonshirley:20200101141631p:plain

自作した、花火みたいなエフェクトを表示させる描写モード2。 f:id:tosonshirley:20200101142018p:plain f:id:tosonshirley:20200101142052p:plain

簡単に説明すると、Kinectが人を認識していないとき待機モードになる。
人を認識すると描写モードに遷移する。現在時刻が0~19分の時、描写モード1を表示。現在時刻が20~59分の時、描写モード2を表示する。
例えば17:09の時、描写モード1を表示。10:34の時、描写モード2を表示する。

描写モード1では、左腕の肘より左手の位置が高い時、時計回りに玉が回転する。逆に左腕の肘より左手の位置が低い時、逆時計回りに玉が回転する。

描写モード2では、両手を左右に動かすと玉の生成位置が移動する。X軸(横方向)しか取得していないため上下は反応しない。

動画が残っていたため以下に添付する。
(描写モード1の動画はどこかに無くした...)


補足説明として、 待機モードから描写モードに移るときに来場者人数をカウントする。その際、カウントした値はUnity内で保管されるわけではなく、localhostPHPサーバー上に保管される。相方が実装したのでよく分からないが、インターネット環境下で外部にPHPサーバーを置けば、外部から今来場者が何人来たか確認できるらしい。サーバー上に保管することによって、一旦アプリケーションを閉じて、再度実行しても来場者数はリセットされずにカウントを続けることができる。


描写モード1では、ほとんどスクリプトを書いていないので、描写モード2について書こうと思うが、長くなりそうなので物体生成のスクリプトだけ説明する。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlowerCircle : MonoBehaviour
{
    // このコルーチンの処理を待たせる時間
    public float WaitTime = 0.5f;

    public float AllWaitTime = 1.0f;

    public float WaitobjectTime = 0.05f;
    // 周りに生成するオブジェクト
    public GameObject[] objects;
    // 周りに生成するオブジェクト (その子供)
    public GameObject[] childobjects;
    // 関節の番号 (自分で振る)
    public int JointNumber = 0;

    // オブジェクトを削除する時間
    public float ObjDeleteTime;
    // 子供のオブジェクトを削除する時間
    public float childObjDeleteTime;
    // 周りに生成するオブジェクト数
    public int circleObjMax = 12;
    // 座標の中心の object
    public GameObject BaseObj;
    // copy する object
    private GameObject CloneObj;
    private Rigidbody[] rb;


    // Use this for initialization
    void Start()
    {

        objects = new GameObject[50];
        rb = new Rigidbody[50];
        childobjects = new GameObject[50];
        // オブジェクトを削除する時間
        ObjDeleteTime = WaitTime * 11f;
        childObjDeleteTime = WaitTime * 10.05f;

        // circle 生成処理
        StartCoroutine("OnCreateCircle");
    }

    IEnumerator OnCreateCircle()
    {
        //円の大きさ
        int CircleDouble = 2;

        while (true)
        {
            if (DataCenter.IsDetected[JointNumber] && DataCenter.GameMode == 1)
            {
                // プレイヤーの座標取得 (更新)
                Vector3 basePos = BaseObj.gameObject.transform.position;
                // 周りのオブジェクトを生成
                for (int circleObjIdx = 0; circleObjIdx < circleObjMax; circleObjIdx++)
                {
                    CloneObj = (GameObject)Resources.Load("MovingCreate");

                    // 正規化されたベクトル
                    Vector3 objVec = new Vector3(
                        CircleX(circleObjIdx, circleObjMax, CircleDouble),
                        CircleY(circleObjIdx, circleObjMax, CircleDouble),
                        0
                    );

                    // 周りの円の位置を計算(1回目)
                    Vector3 objPos = basePos + objVec;
                    // オブジェクトを生成
                    objects[circleObjIdx] = Instantiate(CloneObj, objPos, Quaternion.identity);

                    //rigidbody取得
                    Rigidbody rb = objects[circleObjIdx].GetComponent<Rigidbody>();
                    //オブジェクトに放射状に力を加える
                    rb.AddForce(objVec * 100);

                    // 作ったオブジェクトを一定時間後に消す
                    Destroy(objects[circleObjIdx], ObjDeleteTime);

                    // fade outを行う
                    StartCoroutine("FadeOut", objects[circleObjIdx]);

                }

                yield return new WaitForSeconds(WaitobjectTime);

                // 一回目に生成した円中心に再度描写
                for (int circleObjIdx = 0; circleObjIdx < circleObjMax; circleObjIdx++)
                {
                    for (int circleChildObjIdx = 0; circleChildObjIdx < circleObjMax; circleChildObjIdx++)
                    {
                        // 正規化されたベクトル
                        Vector3 objChildVec = new Vector3(
                            CirclechildX(circleChildObjIdx, circleObjMax, CircleDouble),
                            CirclechildY(circleChildObjIdx, circleObjMax, CircleDouble),
                            0
                        );

                        // 周りの円の位置を計算
                        Vector3 objChildPos = objects[circleObjIdx].gameObject.transform.position + objChildVec;

                        // オブジェクトを生成
                        childobjects[circleChildObjIdx] = Instantiate(CloneObj, objChildPos, Quaternion.identity);
                        //rigidbody取得
                        Rigidbody rbc = childobjects[circleChildObjIdx].GetComponent<Rigidbody>();

                        //オブジェクトに放射状に力を加える
                        rbc.AddForce(objChildVec * 100);

                        Destroy(childobjects[circleChildObjIdx], childObjDeleteTime);
                        // fade outを行う
                        StartCoroutine("FadeOut", childobjects[circleChildObjIdx]);

                        yield return new WaitForSeconds(WaitobjectTime / circleObjMax);
                    }
                }
            }

            yield return new WaitForSeconds(AllWaitTime); // n 秒処理を待つ
        }
    }

    IEnumerator FadeOut(GameObject obj)
    {
        Vector3 objVecSub = new Vector3(0.2f, 0.2f, 0);

        for (int i = 0; i < 5; i++)
        {
            obj.transform.localScale = obj.transform.localScale - objVecSub;

            yield return new WaitForSeconds(1);
        }
    }

    //一回目に円状に生成される玉のX座標
    float CircleX(int circleObjNum, int circleObjMax, int Double)
    {
        float angle = circleObjNum * 360 / circleObjMax;
        float x = Mathf.Sin(angle * (Mathf.PI / 180));
        return x * Double;
    }

 //一回目に円状に生成される玉のY座標
    float CircleY(int circleObjNum, int circleObjMax, int Double)
    {
        float angle = circleObjNum * 360 / circleObjMax;
        float y = Mathf.Cos(angle * (Mathf.PI / 180));
        return y * Double;
    }

 //一回目に生成された円を中心にして再度円状に生成される玉のX座標
    float CirclechildX(int circleObjNum, int circleObjMax, int Double)
    {
        float angle = circleObjNum * 360 / circleObjMax + 360 / circleObjMax / 2;
        float x = Mathf.Sin(angle * (Mathf.PI / 180));
        return x * Double;
    }

 //一回目に生成された円を中心にして再度円状に生成される玉のY座標
    float CirclechildY(int circleObjNum, int circleObjMax, int Double)
    {
        float angle = circleObjNum * 360 / circleObjMax + 360 / circleObjMax / 2;
        float y = Mathf.Cos(angle * (Mathf.PI / 180));
        return y * Double;
    }
}

玉が一斉に表示されてグチャグチャにならないように、コルーチンを使って玉が生成される時間に差をつけることでアニメーションを作った。(コルーチンの変数名が適当なのは申し訳ない...)


プレイヤーの座標取得
プレイヤーの取得座標を中心に円状に玉を配置して放射状に力を加える
WaitobjectTime秒待つ
最初に生成された玉1つを中心に玉を円状に玉を配置して放射状に力を加える。
(玉1つ生成毎にWaitobjectTime / circleObjMax秒待つ)
再度、最初に生成された玉1つを中心に玉を円状に玉を配置して放射状に力を加える。
(玉1つ生成毎にWaitobjectTime / circleObjMax秒待つ)
(最初に生成された玉の数(circleObjMax)回繰り返す。)
AllWaitTime秒待つ
最初に戻る


文章だとわかりにくいので図で簡潔に説明してみる。

circleObjMax=3のとき
最初、プレイヤーの取得座標を原点と仮定すると下図のようになる。 f:id:tosonshirley:20200105181548p:plain

次に最初に生成された玉を中心に再度下図のように描写。 f:id:tosonshirley:20200105181917p:plain

みたいな感じ...
circleObjMax(玉の数)を変えても均等に配置される。

今回初めてgithubを使って共同制作した。複数人で1つの作品を制作-公開するにはすごく便利なツールだなと思った。(小並感)

他の説明は相方に任せます。
スクリプトは相方のgithub上で公開してます。

github.com

fusion360でトルクの概算

 fusion360って素材をを指定すると、勝手に質量と重心の慣性モーメントを計算してくれるみたい。 これを元にモーターにかかる負荷慣性モーメントを計算し、必要トルクを概算してみる。

 まず、コンポーネントを右クリックしてプロパティを開く。
注意点として、測りたいコンポーネントを親コンポーネントにまとめる。
コンポーネントをまとめないで、複数選択でプロパティ開いても、1つのコンポーネントの数値しか表示されない。

f:id:tosonshirley:20191231223211p:plain

すると、下図のように質量と慣性モーメントが表示される。

f:id:tosonshirley:20191231224505p:plain

クリップボードにコピーをクリックしてメモ帳か何かにペースト。

f:id:tosonshirley:20191231225053p:plain

今回は足をZ軸方向に平行な軸を中心に回転させるので、重心のZ軸を平行移動させる。
平行軸の定理が使えるので、重心と回転させたい軸との距離dを求める。

平行軸の定理


I=I_g+Md^2


距離はツールバーの検査-計測から測れる。

f:id:tosonshirley:20191231230749p:plain

数値は


M=104.695(g)\\
d=68.593(mm)\\
I_g=I_{zz}=1.645×10^5(g\cdot mm^2)

であるから、


I=6.571×10^5(g\cdot mm^2)=6.571×10^{-4}(kg\cdot m^2)

モーターにかかる負荷慣性モーメントが導出できた。

今回モーターはMG92Bを使用する。
6.0Vでストールトルク(起動トルク)は3.5(kgf\cdot cm)である。摩擦云々考慮して、0.8掛けで2.8(kgf\cdot cm)=2.8×10^{-2}(kgf\cdot m)=0.27(N\cdot m)とする。

MG92Bの無負荷動作速度が0.08秒/60°(6.0V)。13rad/sであるから
0.1秒で10rad/sに加速出来ればロボットの動作には影響はないと考える。
初めの角速度をω1、終わりの角速度をω2とする。

必要トルクTは


T=I\cdot \frac{(ω_2-ω_1)}{t}

である。

計算して


T=I\cdot \frac{(ω_2-ω_1)}{t}=6.571×10^{-4}\cdot \frac{10}{0.1}=6.571×10^{-2}(N\cdot m)

である。

6.571×10^{-2}<0.27

であるからモーターのトルクは十分であるといえる。
合ってるかなこれ...


参考文献

http://www.mekatoro.net/digianaecatalog/panas-geared/book/panas-geared-P0025.pdf

工業力学入門講座(第22) 慣性モーメントと加速トルクと加速時間

Python venv備忘録

python3.5からvenvが主流になったらしい。また、anacondaのサイズがでかすぎるのでこれからはvenvを主に使っていこうと思う。 備忘録としてまとめる。

環境作成

[project dir] $ python3 -m venv [newenvname]

newenvnameはvenvを推奨。IDEでvenvを管理できる。

Activate

$ . [newenvname]/bin/activate

sourceを.に置き換えることができるらしい。初耳。

パッケージインストール

(newenvname)$ pip install [package name]

インストールしたパッケージの確認

(newenvname)$ pip freeze

requirements.txtインポート

(newenvname)$ pip install -r requirements.txt

requirements.txtエクスポート

(newenvname)$ pip freeze > requirements.txt

Deactivate

(newenvname)$ deactive

環境初期化

deactivate後に行う。

$python3 -m venv --clear [venvname]