あえて賢かれ

自分用の備忘録

Miceで主流な壁情報の記録方法

壁情報の記録方法の記事が少ないということで書きます.

以下の記事の方法の話です.
壁情報の記録 - hantas's blog

マイクロマウスを完走させるうえで一番初めにビット演算などが関わってくるのが壁情報です.ビット演算と聞いて機械工でプログラム初心者の僕はすごく嫌な気分になりました.同じような人に少しでも助けになればと思います.お酒入れているので間違ってたらすいません....

壁の数

クラシック競技の迷路は16×16区画で,壁は2×16×17枚あります.すべての壁の有無を記憶するためには, 2×16×17bit必要です.
f:id:staytus:20190913232340p:plain
4×4の迷路を考えると,水平(Horizontal)な壁が4枚×5行分,垂直(Vertical)な壁が4枚×5行分,計2×4×5枚分あるのがわかる思います.

縦壁と横壁で記録する

f:id:staytus:20190913232641p:plain

言葉で説明するのもつらいので,図を見てください.垂直な壁と水平な壁に分けて考えて,壁がないことを0,壁があることを1と表し,各ビットを一枚の壁に対応させます.

任意の座標の任意の方向の壁はどこの配列のどこのビットに対応するのか考える

f:id:staytus:20190913233026p:plain

まず注意として,以下では最上位ビットから数えて何bit目かという表現をします.2進数で1000という数があったとすると,1は最上位ビットから数えて0bit目,0100という数があったとすると,1は最上位ビットから数えて1bit目という調子です.上図を見てください.座標(3,2)の東西南北の壁はどの配列の何bit目に対応しているでしょうか...北壁はwallH[3]上位から3bit目,西壁はwallV[3]上位から2bit目,南壁はwallH[2]上位から3bit目,東壁wallV[4]上位から2bit目に対応してますね?(間違えてたらすみません...お酒のせいです)

さてこれを一般化して,座標(x,y)であったら,どうでしょうか.

f:id:staytus:20190913233746p:plain

少し考えると上図のようになりますね?(間違えてたらすみません...お酒のせいです)

ということで,任意の座標の任意の方向の壁はどこの配列のどこのビットに対応するのかわかりました.あとは実装するのみです.頑張って.

16×16区画だと必要な配列数は?

4×4の迷路であれば,wallHとwallVという2進数4桁の配列は[0]から[4]の5個必要でした.ということは16×16の迷路なら17個の2進数16桁の配列があればよさそうです.まずはそのようにして実装すればよいと思います.マイクロマウスの競技の規定上一番外の壁は必ずあります.そのように事前にあることが確定している壁については壁情報用の配列を用意しなくてよいので,本当は垂直と水平それぞれで15個の配列があれば十分かもしれません.しかし,今後,(x,y)座標における北壁があるのか確かめる関数等を実装する必要があります.そのような関数を作る際,例外処理が必要になるので,まずは17個の配列を用意して壁情報に関わる関数の動作を一通り確認し終えたら,不必要な情報は持たないようにしていけばいいんじゃないかと個人的には思います.(僕はいまだに未実装...)

実装の方法

16×16マスであれば,17個の2進数16桁の配列があればよさそうなので,uint16_tの配列を垂直と水平方向で2つ用意します.最上位ビットだけが1で他が0の16桁の2進数を16進数で表すと0x80です.南壁を例にとると,wallH[y]上位からxbit目の数を1としたいです.uint 16_t one=0x80;として,このoneを右にxだけビットシフトして,0で初期化したwallH[y]と(one>>x)をor演算すればいいんじゃないでしょうか.それでは,壁を入れることしかできませんが,その辺は自分の宗教によるところだと思います.(僕は壁を入れることしかできません.入れ間違えたら終わりです.)

完走に必要な壁情報に関わる関数

⓪ 壁の初期化関数(外壁とスタートの横の壁は1,他は0に初期化)
① 座標と方位を与えたらその場の壁の配列に壁を入れる関数
② ある座標と向きにおいて左と前と右センサの値が閾値を超えていたら,その方向の壁に壁を入れる関数
③ 座標と向きを与えたらその場の壁があるかどうか判定する関数
④ すべての壁情報を吐き出す関数

...ここまでが足立法実装に必要な関数だと思います.この後は,完走から先に必要になるもの.
⑤ 既知壁の記録,判定,出力の関数→最短走行,既知区間加速,未知区間優先探索に
⑥ 未知壁をすべてあるものとして壁を入れる関数→最短走行に
⑦ 入れた未知壁を取り除く関数→最短走行後の探索に

いっぱいありますね...一発ですべてを理解して,実装するのは厳しいので,一つ一つ作っていきましょう...
お酒もまわってきたので,この辺で...

関数でスラロームを実装する

マウス合宿でブログを書くように煽られてしまったので,書きました...

関数でスラロームを実装しようって話です.

なぜ関数で実装する必要があるかというと,スラロームなどをするとパラメータの調整が必須でいちいち加速テーブルなんて作っていられないっとおもうからだと思います.(Miceでは加速テーブルをつかわないので,よくわかってません.)

大回りと斜めの走行も考えると,左右で同じパラメータを使ったとしても

①普通の90度スラローム

②大回り90

③大回り180

④45度入

⑤45度出

⑥135度入

⑦135度出

⑧v90

とたくさんあります.

すべてを調整する必要があるので,まあ地獄.



f:id:staytus:20190709065327p:plain
昨年度作ったターン用の関数群
ターンについて詳しくは,けりさんのブログで.


kerikeri.top


とまあこんな感じでターンはたくさんあるので,角度,重心速度,角加速度,最高(最低)角速度を引数として持つスラローム用の関数を作成しようってなります.

 

スラロームを詳しく知りたい方は以下をご参照.


tenpura.sugo-roku.com



重心速度をvとして,右タイヤの速度をv+Δv,左の速度をv-Δvとすれば,前に進みながら左に旋回しますよね.トレッドの半分をrとすれば,この時の角速度はΔv=rωの関係から求まります.速度を連続的に変化させないと脱調などの問題となるので,Δv(すなわちω)を台形加減速すればいいって話です.

 メイン関数でやるべきことは,

①割り込み許可

②目標角度に達するまで待機

制御周期ごと割り込ませる関数でやるべきことは

①角速度ωを計算する

②角度θを計算する

③右のタイヤの速度をv+rω,左のタイヤの速度をv-rωとする

です.

 
実装のイメージ

//制御周期ごとに割り込ませる関数

void AccelerateMotor_Turn(void) {
	static uint32_t counter = 0;
	static uint16_t process = 0;
	static float 減速開始距離 = 0; 

//条件判断

	if (左旋回の場合) {

		if (現在角 >= 目標角) { 
				process = 終了;

		} else if (counter == 0) {
			process = 初期化;
			counter++;
		} else if (現在角 < 目標角 / 2) {
			if (現在角速度 <最大角速度) {
				process = 加速;
			} else {
				if (process == 加速) {
					減速開始角度 = 目標角 - 現在角;
				}
				process = 維持;
				counter++;
			}
		} else {
			if (現在角 >= 減速開始角度) {
				process = 減速;
				counter++;
			} else {
				process = 維持;
				counter++;
			}
		}

	} else {//右旋回の場合

		if (現在角<= 目標角) {
				process = 終了;
				counter = 0;

		} else if (counter == 0) {
			process = 初期化;
			counter++;

		} else if (現在角 > 目標角 / 2) {
			if (現在角速度 >目標角速度) {
				process = 加速;
				counter++;
			} else {
				if (process == 加速) {
					減速開始角度 = 目標角 - 現在角;
				}
				process = 維持;
				counter++;
			}
		} else {
			if (現在角 <= 減速開始角度) {
				process = 減速;
				counter++;

			} else {
				process = 維持;
				counter++;
			}
		}

	}

//条件に基づく動作

	switch (process) {
	case 初期化:
		減速開始角度=0;
		現在角=0;
		現在角速度=初角速度;
		//速度変更
		Change_Duty(LEFT,
				calculate_Duty_L(重心速度, 現在角速度));
		Change_Duty(RIGHT,
				calculate_Duty_R(重心速度, 現在角速度));
		//角度計算
		現在角 += 現在角速度 * 制御周期;	
		break;
	case 加速:
		//角速度計算
		tar.omega += 角加速度 * 制御周期;
		//速度変更
		Change_Duty(LEFT,
				calculate_Duty_L(重心速度, 現在角速度));
		Change_Duty(RIGHT,
				calculate_Duty_R(重心速度, 現在角速度));
		//角度計算
		現在角 += 現在角速度 * 制御周期;
		break;
	case 減速:
		//角速度計算
		現在角 -= 角加速度 * 制御周期;
		//速度変更
		Change_Duty(LEFT,
				calculate_Duty_L(重心速度, 現在角速度));
		Change_Duty(RIGHT,
				calculate_Duty_R(重心速度, 現在角速度));
		//角度計算
		現在角 += 現在角速度 * 制御周期;

		break;
	case 維持:
		//角度計算
		現在角 += 現在角速度 * 制御周期;
		break;
	case 終了:
		counter = 0;
		process = 0;

		//割り込みでこの関数が呼ばれないようにする
		g_mode_it == STANDBY;
		break;
	default:
		break;
	}

}

//メイン関数で使う関数

void Turn(float 目標角, float 初角速度,
		float 角加速度, float 重心速度, float 最大角速度) {
	//AccelerateMotor_Turnで使う変数(目標角,初角速度,角加速度,重心速度,最大角速度)を変更

	//重心速度は正で,それ以外については左旋回の場合は正,右旋回の場合は負

	・・・

	//割り込みでAccelerateMotor_Turnを呼び出すようにする
	g_mode_it = TURN;
	
	while (g_mode_it == TURN);

}




僕はこんな感じの実装をしています.重心速度が一定であれば,台形加減速のプログラムより簡単です.
他人のプログラムを読んだことがほぼないので,もしかしたらガラパゴス化しているかもしれません.
何かまずそうなことやもっとここを説明しろと思うことがあればご連絡ください.




 

 

 

MIDI音源を編集してスピーカで鳴らすまでの話

マイコンでスピーカを鳴らしてみました.

くそ雑魚なので,偉大な先輩方の技術を学ばせていただいています.このブログより以下のブログの方が役立つと思います.

音楽的な知識がないので多分適当なこと書いていると思います.その点もご了承ください...

 

 

<参考記事>

[1]STMのPWMでスピーカを鳴らす&スピーカに必要な関数について

umuoumu.blog.fc2.com

[2]スピーカ周辺の回路

ayatakaworks.wixsite.com

[3]MIDI音源を使ったスピーカ用音源の作り方について

 ayatakaworks.wixsite.com

 

スピーカで音を鳴らす際に必要となる要素は,

  • 音の高さ(周波数)
  • 音の長さ(待ち時間)

です.マイコンで音源を鳴らすためには,最低この二つの情報を配列か何かで事前に持っている必要があります.音の大きさについてはDuty比をいじることで変えることはできると思いますが,Duty比は基本的に1/2で固定するので大きさについては変更しません.

 

音の高さと音階について

音の高さは周波数です.ISOによって制定された?基準周波数はA4の440 Hzです.音階というのは感覚的なもののように感じますが,一オクターブ上がるごとに周波数は2倍になり,その間の音は12等分されています.

 

すなわち,1音上がることに,周波数は2^{1/12}倍されて,

1オクターブ上がって倍音となると,周波数は2倍となります.

 

プログラムで実装する際は,計算誤差と計算時間を考えなければ,事前に音の周波数をすべて書き込まなくても,for文を回して2^{1/12}\approx1.0594631を基準周波数からの音階番号の差の分だけ掛ける(または割る)ことで得ることはできます.

 

音の長さについて

音楽を鳴らす速度についてbpm(beat per minute)というものがあります.一分間に鳴らすビート(拍)の数というのはいいんですが,拍がどのくらいの長さなのか決まっていないっぽい?ということで,僕はよくわからないので四分音符♪を1拍分としました.MIDIでは音の長さをtickという単位で記録しますが,四分音符が何tickかは決めなければなりません.僕は,480tickを四分音符の長さとしています.

60 bpmの場合,一分間に四分音符が60回なります.だから,四分音符の長さは 1 sです.

一般化して,

四分音符の長さ =\frac {1000×60} {bpm} ms

1tickの長さ=\frac {1000×60} {480×bpm} ms

 

となります.

 

MIDI音源を配列化する

あやたかさんのブログを参照してください.

 

ネット上のMIDI音源を配列化するためには,MIDI音源から音の高さと音の長さの情報を得る必要があります.加えて多くの音源は複数の楽器を用いているので,トラックが分かれていて,同時に複数の音が鳴ります.1つのスピーカーでかつ簡単な矩形波では単音しかならせないので,音源を適当な形に編集する必要があります.

 

需要があるかは知りませんが僕がした方法を載せておきます.

 

MIDI編集ソフト世界樹のインストール

フリーでオープンソースのMIDIシーケンサー・MIDI編集ソフト『世界樹』

 

②単音のみになるように音源の編集

f:id:staytus:20190519222051p:plain一つのトラックに主旋律を拾い集める.

 

③イベントリスト(ノートオンのみにチェック)をCSVでエクスポート

f:id:staytus:20190519222527p:plain

右側のチェックの欄でノートオンのみにして,CSVで吐き出し.

 

④エクセル上で配列としてコピペできる状態にする.

f:id:staytus:20190519225856p:plain

 

R列に{音階番号,音を鳴らす時間[tick],全体の時間(音を鳴らす時間+その後の待ち時間)[tick]}をまとめて,コピペできるようにした.

 

四分音符の長さを480tickとすると,1拍は480tick,1小節は4×480tickになるはずです.

 

⑤配列の初期化の部分にコピペで実装.

 

クオリティを求めるとブログを公開しなくなりそうなので,適当な記事でお許しください...そのうち気が向いたら割り込みで実装する話とか具体的なプログラムの話とかを書こうと思います().

 

 

 

 

Kinema発注しました

新作マウスKinema(キネマ),やっと大体の部品の発注できました.(3/28)

2代目のマウスはDC吸引4輪マウスです.まあ定番のやつです.普通と違うのは,センサの数,吸引ファンと1717の配置,遊び歯車の存在くらいです.

 

参考までに(というより備忘録として)使用したソフト,サービス,部品購入先と注意事項等を並べておきます.

  

  • 使用ソフト

・3DCAD Fusion360

・回路CAD Eagle

 

  • 発注先

・3Dプリントサービス DMM (ナイロンと光造形樹脂)

・PCBサービス Elecrow

 

・モノタロウ ベアリング・スパーギア

・ネオヘリ ピニオンギア 

・ウィルコ ねじ部品・シャフト

・エアクラフト 吸引モータ・リポ

・デジキー 電子部品

秋月電子 電子部品(LED・スピーカー・スライドスイッチ)

・RT フォトトランジスタ

・双葉産業 ナイロンナット・シムリング・タイヤ

 

①足回りの設計

外部リンク:DC初心者のための足回り設計に関して - ゴール座標は(7,7)に!

外部リンク:1717使用マウスの足回り制作 - ぱわぷろ活動日誌

  1. ギアのバックラッシ(モジュール0.5の場合は0.1mm程度)を考慮する.
  2. ギアがタイヤのゴムと接触しないように緩衝地帯を設ける.
  3. ギアの歯先円がタイヤの外径より大きくならないように気を付ける.
  4. タイヤのゴムの厚みを考えて設計する.

②回路設計

・Eagle

 ・Schematic

  1. 回路はデータシートの応用例やほかのマウサーの回路を参考にして作る.コンデンサの容量とかもしっかりデータシートを読み込めば書いてある.
  2. 部品の情報はRSコンポーネンツのサイトからPCB part Library を利用するとフットプリントとかを自作しなくて済む.

    参考サイト:EAGLEとFusion360の連携(AUTODESKの犬になろう) - nacarの独り言

  3. 例えば,本来5Vの線であるはずなのに誤ってGNDとNameをつけてしまった場合.Nameで5Vの名前をGNDに変更すると,5VとGNDが短絡してしまうことになるので注意する.→その配線を消去して配線しなおした.そんなことはなかったです.ごめんなさい!!

 ・Board

  1. BoardとSchematicの連携は切らないように気を付ける.
  2. Fusion360から基板外形を取り込み,回路の情報をプッシュできる.(位置決め用の線とかはEagleで自分で書き込む必要あり)

    外部リンク:EAGLEとFusion360の連携(AUTODESKの犬になろう) - nacarの独り言

  3. シルクの印刷されるレイヤーを確認しておく.

    外部リンク:Eagleで使用するレイヤ

  4. 位置決め用の線は例えばtDocuなどのレイヤーに書く(この場合シルクで印刷されない)
  5. 基板発注先(例えばElecrow)のデザインルール等をダウンロードする.

    外部リンク:EAGLEとFusion360の連携(AUTODESKの犬になろう) - nacarの独り言

  6. コマンドに例えばC14と打てば,該当のコンデンサが飛んでくるので探さなくて済んで便利.
  7. 自動配線はGND等の大電流の流れる線を先に手配線して,信号線のみを自動配線した.
  8. 自動配線する前の状況に戻したいことが多々あったので,自動配線する前に名前を変えて保存していた.
  9. 1Aの電流に対して1mm以上の太さを基本として,信号線は0.2mmとした.
  10. はんだ付けしやすいよう必要に応じてパッドを大きくする.
  11. ビアやベタGNDについてはネットの情報を参考にした.

    外部リンク:ノイズ対策.COM|プリント基板の回路設計者・開発者のための技術サイト

  12. 大電流の流れるところのビアは大きくかつ複数に.

    外部リンク:電源配線のビア数は電流容量に合わせて設定する | ノイズ対策.com

  13. ベタGNDのクリアランスは設定した方がよい.

    外部リンク:http://blog.livedoor.jp/yz333/archives/45891935.html

③基板発注

・Elecrow

  1. EagleのtValue,bValueはシルク印刷されないので,印刷したい場合はレイヤーを移動.
  2. ガーバデータをeagleで作成し,必要なものだけ取り出して,zipにしてアップロード.(難しそうに感じるが,わかればかなり簡単)

    f:id:staytus:20190329171044p:plain

    必要なガーバデータ(elecrowの発注画面)



  3. fusionPCBにアップロードすれば簡単にデータをプレビューして,穴やシルクの位置を確認できる.

    f:id:staytus:20190329171022p:plain

  4. 発注の選択例(詳しくはわかりませんし正しいかもわかりません).配送方法:小心者はOCS/ANAを選択.

    f:id:staytus:20190329171358p:plain

支払いはクレジットカードとペイパルが必須だと思いました.なんとか春休み中の発注が間に合って一安心です.これで基板出走ができます.新入生教育もあって見通しが立ちませんが,土日で作業して6月のプチ大会に...と思っています.

 

【Meister振り返り①】任意の速度,距離での台形加速

※この記事は自分向けの備忘録です.台形加速についてなかなか簡単にプログラムがかけたと思っていたので,資料を残そうとMeisterのプログラムを見直したら,台形加速が間違っていたことに気づいてしまいました.記事を信用しないでください.

 

任意の速度,加速度,距離で台形加速できること,任意の角速度,角加速度でスラロームできることは,様々な動作を必要とする斜め走行には必要となってきます.スラロームは角加速度を台形加速してあげればよく,かつはじめと終わりの角速度はともに0でより簡単なので,初期の速度と終端速度が異なる台形加速ができることが難所となると思います.

 

引数

初期速度: v_{start} 

最高速度: v_{max} 

終端速度: v_{end}

加速度: a 

重心の進行距離: x

目標距離: x_{target} 

 

言葉の定義

台形加速の加速終了時点での進んだ距離: x_{accelend}

台形加速の減速開始時点での進んだ距離: x_{decelstart}

三角加速の最高速度: v_{max}'

三角加速の最高速度到達時点の進んだ距離: x_{judge}

 

なお,今回の記事内では初期の距離は0から始まるものとしています.

f:id:staytus:20190329143627p:plain



  • まず台形加速を考える 

 ①加速

初期速度から最高速度に達するまで加速すればよいです.

制御周期ごとに速度を加速度×制御周期だけ加算します.

加速の終わりの時点の進んだ距離x_{accelend}を記録します.

 ②速度維持

減速開始距離まで速度を維持します.

ただ,減速開始距離は計算で求める必要があり,

今回は加減速ともに同じ加速度(減速度)ということに着目して,

 減速開始距離=x_{target}-減速に要する距離=x_{target}-加速に要する距離× \frac{減速に要する距離l_{decel}}{加速に要する距離l_{accel}}

 

すなわち,

 x _ {decelstart}=x_{target}-x_{accelend} \frac{v_{max}^2-v_{end}^2}{v_{max}^2-v_{start}^2}

となります.

 

(∵等加速度直線運動の式:v^2-v_0^2=2alより,

\frac{減速に要する距離l_{decel}}{加速に要する距離l_{accel}}=\frac{v_{max}^2-v_{end}^2}{v_{max}^2-v_{start}^2})

 

v_{max}^2=v_{start}^2では発散するので例外処理します.

 ③減速

減速開始距離から目標距離x_{target}に到達するまで減速します.

 

  • 三角加速について

 ①加速

 三角加速の最高速度到達時点の進んだ距離まで加速します.

これは,計算して求める必要があり,

x_{judge}= \frac{1}{2}x_{target}+ \frac{1}{4a}(v_{end}^2-v_{start}^2)

です.

 (∵

v_{max}'^2-v_{start}^2=2al_{accel}

v_{max}'^2-v_{end}^2=2al_{decel}

l_{accel}+l_{decel}=x_{target}

以上3式より,

x_{decelstart}=x_{accelend}=l_{accel}について解く.)

②減速

減速開始距離から目標距離x_{target}に到達するまで減速します.

 

  • 台形加速と三角加速を一つにまとめる

0⃣初期化

すすんだ距離x,減速開始距離x_{decelstart}を初期化し,判定点x_{judge}を計算する.

1⃣x_{judge}より距離が小さい時,

①最高速v_{max} 以下なら加速する

②最高速v_{max} に達したなら速度を維持する.

 ②になったとき加速終了時点の距離x_{accelend}を記録し,

 上記の計算式より減速開始距離x_{decelstart}を求める.

2⃣x_{judge}より距離が大きい時,

③減速開始距離x_{decelstart}までは速度を維持する.

④減速開始距離x_{decelstart}以降は,減速する.

 

※三角加速のとき減速開始距離x_{decelstart}は計算されない(1⃣の②の条件に入らないため).しっかりと0で初期化しておく必要がある.

※負の加速度,正の減速度には対応していない.加速だけしたいときは,v_{end}=v_{max},減速だけしたいときは[v_{start}=v_{max}]とする.

 

こんな感じで4つの場合分けによって台形加速と三角加速のプログラムが書くことができると思います.台形加速のプログラムで根号を使った計算を避けることを1番に考えているので,拡張性はあまりないと思います.