DATE: 2018/09/19(水)   CATEGORY: Nucleoマウス
Nucleo-32boardを使ったクラシックマウスの開発その23 ~壁情報とFLASH~


今回は迷路データの保存についての話です。
前回で基本的な調整が終わり、
迷路を走ることができるようになりました。
また,任意座標で止まれるようになっているということは
マウスの位置を把握できているということですね?
つまり、その位置の壁情報を覚えることも可能になっているわけです。

ということで壁情報を保存して迷路を覚えさせましょう。
→マウスっぽくなってきた


・壁情報の形式

壁情報を保存するにあたりその保存形式を決めなければなりません。
以下に自分が知っている具体例を挙げていきます。
メリットとデメリットを考慮してどの形式で保存していくか
自分で選択するのがいいと思います。

→正直好みの問題

どの形式でも共通していることは
①壁の有無情報、②壁の探索情報
の2つを保存していくという点です。
→探索情報は最短走行の際に非常に重要となります


壁情報については多くの人が書いてくれているので
調べればたくさんでてきます。


① 1区画につき8 bit形式

上位4 bitを探索情報、下位4 bitを有無情報としてとる方法。
参考はこちら。図も書いてくれているのでわかりやすいです。

迷路情報の考え方~WMMC秘伝のタレ 前編~ -ぱわぷろ技術日誌

よく使われている手法です。
自分も昔はこれを使っていました。

16×16の迷路だと256 byte使います。
→32×32だと4倍の1024 byte

メリットは区画単位で考えられることでしょうか。
区画単位で考えられる分理解しやすいです。

デメリットは区画単位で壁情報を保持するため、
一つの壁に対して2 面分のデータを保存しないといけない点です。
壁情報が重複している分無駄にメモリを使います。
そのため使用メモリが他の方法に比べ多くなってしまいますが、
RAM領域的には十分確保できるため
使用メモリについてはあまり気にしなくてもいいかもしれません。

自分は壁の両面保存が好みでは無かったので止めました。
他は特に不満はありませんでした。


② 北壁及び東壁のみ形式

北壁と東壁のみを保存していく形式です。
ある区画を考えたときに、
その区画の南壁は一つ下の区画の北壁を参照すれば問題ありません。
その区画の西壁は一つ左の区画の東壁を参照すれば問題ありません。
この特徴に注目したのがこの形式です。

基本的には16 bit変数(32×32対応なら32 bit変数)を用いて
壁データを保存していきます。

定義を書いておくとこんな感じ。


typedef struct {
uint32_t wall;
uint32_t known;
} walldata;

walldata g_easternWallData[32], g_northernWallData[32];


16×16の迷路だと128 byte使います。
→32×32だと4倍の512 byte

基本的にビット番号がx座標を表し、配列の要素番号がy座標を意味します。
→逆でも可
例えば、(x,y)の東壁を入れるときには
東壁データ[y]|=(1<< x)
のようにすることが可能です。
壁を抜くときには(1<< x)の補数とAND演算すればよいですね。

メリットは①の方法に比べ、
使用するメモリがちょうど半分になっていることです。

デメリットは③に示す方法に比べて、
無駄に壁情報を持ってしっまていることでしょうか。
→無駄と言っても変数4つ分

個人的には考え方がわかりやすく、
壁の重複もないことからこの形式を採用しています。



③ 内壁のみ形式

マイクロマウスのルール上外壁の存在は保証されています。
なので、その内壁だけを保存して利用していく考え方です。
参考はこちら。

壁情報の記録 -hantas's blog

僕の迷路クラスの紹介 -KERI`s Lab

個人的なイメージとしては漢字の「井」のような部分だけ保存する感じですかね。

16×16の迷路だと120 byte使います。
→32×32だと496 byte

メリットは②の方法に比べ、東外壁と北外壁も保存しないので
使用メモリが最も少ないことです。
→(東外壁+北外壁=2列)×(有無情報+探索情報=2種類)で壁データの変数4つ分少なくできます

デメリットというものはあまり思いつきませんが、
強いて言うなら縦方向の壁と横方向の壁で向きが違うので
コードを書くときに間違えやすいことでしょうか。
→これも大したデメリットではない


個人的な感想を簡単にまとめますと、
① 1区画につき8 bit形式 :考え方が簡単。使用メモリ多。
② 北壁及び東壁のみ形式 :考え方が簡単。使用メモリ少。
③ 内壁のみ形式 :考え方がやや複雑。使用メモリ最少。


壁情報の保存形式が決まったら壁情報を変更できる関数を用意しましょう。
→自分は②の方法を採用したためこんな感じ

void setWallDataBit(uint32_t *wallY, uint8_t X) {
*wallY |= (1 << X);
}

void clearWallDataBit(uint32_t *wallY, uint8_t X) {
*wallY = (*wallY & (~(1 << X)));
}



・壁情報の出力関数を作る

方針が決まって、
さあマウスに組み込もうと思った人はちょっと待ってください。
まだ大切なものがありません。
それは保存した壁情報を確認する手段です。

そこで壁情報を視覚的に判断できるように
以下のようにTera termに出力できる関数を作りました。

迷路初期化

横方向の壁は- (半角マイナス)×5、縦方向の壁は| (縦線)、
柱は+(半角プラス)で表しています。

→後で区画中央に数字を入れたいので5個分スペースを作っておきます

色分けしたい場合についてはエスケープシーケンスについて調べてください。


迷路の初期情報として、
① 外壁情報
② (0,0)の区画情報(スタート区画は必ず袋小路)
③ ゴール区画の壁無し情報
→これはどっちでもいい
を与えています。

完成したら、手打ちでいいので
好きな位置に壁を入れて出力して動作確認してみましょう。

壁反映確認


・左手法に壁追加関数を組み込む

ここまでできたら、左手法をしながら壁情報を集めてみます。
移動方向を決定するタイミング=区画境界毎に壁データを追加していきます。
壁の判断はセンサー値を使って判断すればよいでしょう。
→壁の有無さえ判断できればいいので閾値は雑に決めてOK

ゴールをしたら壁情報を先ほどの関数を使って出力してみます。


なんと、、、
Tera termに出力しようとUSBケーブルを繋げたら
マイコンにリセットがかかってしまいました、、、

→Nucleo最大の罠

Nucleoボードは、外部電源で起動していても
USBケーブルを繋ぐと自動でリセットがかかってしまう仕様らしいです。


これではせっかくの壁情報の出力ができません
→どうにかしましょう


これを解決するために、
一旦FLASH領域に迷路データを保存し再起動後読み出すという処理をします。

・FLASH領域への保存

STM32F303K8マイコンではリファレンスマニュアルによると、
以下のような2 kBずつのPageが設けられているようです。

FLASHのアドレス

この領域に迷路データを保存しておくと、
マイコンがリセットされてもこの領域の値は保持されます。


ということでこの領域に迷路データを保存していきます。

保存の流れとしては、
① FLASHのLockを解除
② 消去するPageを選択
③ データを書き込み
④ FLASHを再びLock
となっております。

詳しくはHALのマニュアルを読んでください。
簡単に保存関数を作ってみました。
→雑ですがとりあえず書き込めればいいですね


void saveMazeMapFlash(uint32_t addr) {

FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t PageError = 0;

HAL_StatusTypeDef r;

int i;

//FLASH Unlock
HAL_FLASH_Unlock();

//Page Erase
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = addr;
EraseInitStruct.NbPages = 1;

r = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
if (r == HAL_OK) {
printf("Page Erase OK\n");
} else {
HAL_FLASH_Lock();
return;
}

printf("Write Eastern Wall\n");
for (i = 0; i < MAZESIZEMAX; i++) {
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
(uint32_t) (addr + sizeof(uint32_t) * i),
(uint32_t) g_easternWallData[(i % MAZESIZEMAX)].wall);
}

printf("Write Eastern Known\n");
for (i = MAZESIZEMAX; i < 2 * MAZESIZEMAX; i++) {
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
(uint32_t) (addr + sizeof(uint32_t) * i),
(uint32_t) g_easternWallData[(i % MAZESIZEMAX)].known);
}

printf("Write Northern Wall\n");
for (i = 2 * MAZESIZEMAX; i < 3 * MAZESIZEMAX; i++) {
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
(uint32_t) (addr + sizeof(uint32_t) * i),
(uint32_t) g_northernWallData[(i % MAZESIZEMAX)].wall);
}

printf("Write Northen Known\n");
for (i = 3 * MAZESIZEMAX; i < 4 * MAZESIZEMAX; i++) {
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
(uint32_t) (addr + sizeof(uint32_t) * i),
(uint32_t) g_northernWallData[(i % MAZESIZEMAX)].known);

}

//FLASH Lock
HAL_FLASH_Lock();

}


FLASH領域への保存のときに、
自分が嵌ったポイントを書いておきます。

① 適当なPageを消すとプログラムが止まる

FLASH領域に書き込めると言ってもどこにでも書き込んでいいわけではないみたいです。
FLASH領域は基本的にマイコンの動作を書き込む領域(?)と共用らしいです。
→RXでは分かれていた
なので、上の方のPageを消すとプログラムが止まります。

そのため、自分は一番下のPageを使用しています。
Pageの消去の仕様上Page31にはプログラムを書き込まないようにしないといけません。
→事実上FLASH量の制限が62 kBになる

うまくブロックをかけられればいいのですが、
やり方もわからないのでBuild AnalyzerでFLASH領域の使用量を監視しながらプログラムを書いています。

Build Analyzerで今のFLASH領域の使用率を確認しておきましょう。
Build Analyzerに表示されない場合は、
プロジェクトエクスプローラーでプロジェクトを選択すれば表示されます。
→TrueSTUDIOのバグですかね?


② F303K8の一番下のPageの先頭アドレスは0x0800 f800

F303K8のFLASH領域は64 kBです。
つまり一番下のPageはPage 31です。
上の図ではこのアドレスが省略されていて表記されてないんですよね。
自分で計算して求めないといけませんね。


・FLASH領域の読み出し

読み出しは非常に簡単です。
FLASHをLock解除したら、
読み出したいアドレスを指定して読み出すだけです。

書き込んだ位置を読み出すだけなので簡単です。
読み終わったら再びLockしておきます。


・そんなこんなで

無事に迷路データを保存できるようになったので、
壁情報のデバッグができるようになりました。

→走らせて壁情報が間違っていないか調べる
(Tweetは左手法ではありませんがやりたいことはこんなの)





ここまでできれば完走までもう一息です。


スポンサーサイト

コメントの投稿

 管理者にだけ表示を許可する
Copyright © うむ夫の歩み. all rights reserved.