MIRS2009 ソフトウェア製造仕様書 |
MIRS0901-SOFT-0002 |
版数 |
最終更新日 |
作成 |
承認 |
改訂記事 |
A01 |
2009/3/1 |
坂代・戸塚 |
武藤 |
初版 |
1.目的
2.概要
3.動作の流れ
4.ヘッダ一覧
5.関数一覧
6.変数・構造体一覧
7.まとめ
1 目的
本ドキュメントは、ソフトウェア製造仕様をまとめたものである。
2 概要
競技1回目 : 通路通行動作から始まり、白線やDBの状態に応じて、各種仕掛け通過動作に切り替えていく。
競技2回目 : 競技1回目と同様の動作を行う。
角度 :電子コンパスを用いて取得する。
壁との距離 :超音波センサーを用いて取得する。また、タッチセンサーで衝突して測る場合もある。
移動距離 :ロータリーエンコーダーのカウント数から移動距離を算出
3 動作の流れ
競技プログラムは、スレッドで各種センサーの状態を常に監視し、その状況に応じてswitch-case文で関数を切り替える。
以下に、競技プログラムのフローチャートを、実際の関数名を用いて示す。
fig.1
競技プログラムのフローチャート
4 ヘッダ一覧
ファイル名 |
概要 |
競技プログラムで使用するマクロの定義を行っている。 |
|
extern宣言はすべてここの中で行っている。 |
|
グローバル変数をここで宣言している。構造体の宣言もここで行っている。 |
|
構造体の型定義をここで行っている。 |
|
midiノートの番号をマクロで定義していで定義している。 |
画像処理系
ファイル名 |
概要 |
グローバル変数の宣言を行っている。 |
|
extern宣言を行っている。 |
|
数字認識に用いるマクロの定義を行っている。 |
5 関数一覧
競技アルゴリズム系
ファイル名 |
関数 |
概要 |
int
main(int argc , char *argv[]) |
メイン関数 |
|
|
void
act_normal() |
通路通行動作を行う |
void
act_rota() |
ロータリー通過動作を行う |
|
void
act_kobeya() |
小部屋通過動作を行う |
|
void
act_hakusen() |
白線動作を行う |
|
void
act_key() |
鍵と扉通過動作を行う |
|
void
act_DB() |
DBの読み取り(画像処理)を行う |
|
void
act_goal() |
ゴール動作を行う |
|
void
act_ikidomari() |
行き止まり回避動作を行う |
|
void
print_state(int state) |
現在の状態を分かりやすくターミナルにプリントする(デバッグ用) |
|
double
gettime() |
経過時間(OSを起動してから)を出力 |
|
void ini() |
変数・構造体の初期化 |
|
void
ini_onoff_thread() |
on/off IOセンサー監視スレッドの起動 |
|
void
ini_thread() |
超音波センサー監視スレッド、電子コンパス監視スレッドの起動 |
|
void
select_state() |
白線センサーが反応した場合の条件判断 |
シリアル通信系
ファイル名 |
関数 |
概要 |
int
uss_get(int uss_num)
(標準) |
超音波センサーの値を読み取り、それを返す |
|
|
int set_serial_port(int
fd, struct termios *oldtio) (標準) |
シリアル通信に使うファイルディスクリプタの初期設定 |
void
close_serial_port(int fd, struct termios *oldtio) (標準) |
set_serial_portで開いたデバイスのクローズ |
|
int
put_serial_char(int fd, char c)
(標準) |
ファイルディスクリプタで指定されたデバイスに一文字転送 |
|
int
put_serial_string(int fd, char *s)
(標準) |
ファイルディスクリプタで指定されたデバイスに文字列を転送 |
|
int
get_serial_char(int fd)
(標準) |
ファイルディスクリプタで指定されたデバイスから一文字取得 |
|
int
get_serial_char_motor(int fd) |
ファイルディスクリプタで指定されたデバイスから一文字取得(実態はget_serial_charと同じ) |
|
int
ave_uss(int num, int loop) |
超音波センサーの値を複数回取得し、平均値を出力する(現在使用していない) |
|
void
*uss_thread() |
超音波センサーの値を一定時間毎にグローバル変数に代入するスレッド |
|
void
*onoff_thread() |
on/off IOセンサーの値を一定時間毎にグローバル変数に代入するスレッド |
|
void
ini_ds() |
電子コンパスの初期設定を行う |
|
int
ds_get() |
電子コンパスから値を取得する |
|
void
*ds_thread() |
電子コンパスの値を一定時間毎にグローバル変数に代入するスレッド |
|
void
ds_conv() |
電子コンパスの値をあらかじめ決めた規則にしたがって修正 |
駆動系
ファイル名 |
関数 |
概要 |
void
send_byte(int16_t request) |
モータコントロールボードに8bitの情報を送信する |
|
|
void
read_byte() |
モータコントロールボードから8bitの情報を受信する |
void
motor_right(char dir,int8_t speed) |
右タイヤの制御を行う |
|
void motor_left(char
dir,int8_t speed) |
左タイヤの制御を行う |
|
int
lolita(char dir) |
ロータリーエンコーダーの値を返す |
|
|
void
motor_forward(int speed) |
前進する |
void
motor_back(int speed) |
後退する |
|
void
motor_stop2() |
停止する |
|
void
motor_rotation(int speed,int muki) |
その場回転動作をする |
|
int
motor_circle(int speed,int rad,int muki,int r,int l) |
旋回動作をする |
|
int
turn_right(int speed,int rotang,int err) |
右に指定角度だけその場回転する |
|
int
turn_left(int speed,int rotang,int err) |
左に指定角度だけその場回転する |
|
int motor_forward_dis(int
speed,int rotdis,int err) |
指定距離だけ前進する |
|
int
motor_back_dis(int speed,int rotdis,int err) |
指定距離だけ後退する |
|
int
turn_collect(int speed,int err) |
角度補正動作を行う |
|
int
touch_collect(int speed,int muki,int err) |
タッチセンサー反応時の角度補正・位置補正動作を行う |
画像処理系
ファイル名 |
関数 |
概要 |
int
input_DB(int *num1,char *ps1,int *num2,char *ps2,char *col_dir,int
otsuflag,int smoothflag) |
opencvによる数字認識を実行する |
|
void
img_update(char *str, int width, int height)
(標準) |
画像のキャプチャ |
|
void
threshold(IplImage *src,IplImage *dst,int otsuflag) |
画像の2値化 |
|
|
int
labeling(IplImage *src,IplImage *dst1,IplImage *dst2,char *collect,int
*centerpos) |
画像のラベリング |
void
gettrim(IplImage *src,CvRect *rect) |
数字部を内包する最小矩形の座標情報を取得 |
|
void
triming(IplImage *src,IplImage *dst,CvRect rect) |
指定された画像の指定された座標から指定された大きさの矩形を抽出 |
|
void
noisecut(IplImage *src,IplImage *dst) |
画像上部の余分な部分を白で塗りつぶす |
|
void
select_number(IplImage *src1,IplImage *src2,int *num1,int *num2) |
トリムした画像から数字を決定する |
デバッグ・パフォーマンス系
ファイル名 |
関数 |
概要 |
void
beep(float midi_no,int lengh) |
指定された音階・長さの音をビープ音として出す |
|
void
music_start(char fname[100],int beep_s) |
ビープを用いて拡張子 .bp のファイルからメロディを流す |
|
|
void
ini_bgm() |
BGM再生プログラムの初期化 |
void
bgm_change(int num) |
BGMの変更 |
|
void
*bgm_thread(int num) |
BGM再生のスレッド化 |
|
int main(int
argc,char *argv[]) |
BGM再生プログラム |
関数詳細概要
競技アルゴリズム系
int main(int argc , char *argv[])
メイン関数である。なお、実行の際に、引数をつけることにより、小部屋からはじめる、ロータリーからはじめるなどといった操作も可能としている。
競技1回目と2回目で同じ動作を行うようになっている。switch-case文で各種動作関数を使い分けることで競技をクリアする。
void act_normal()
通路通行動作を行う関数である。
超音波センサーによる壁の検知と曲り角の通行、また、タッチセンサー衝突時の回避動作などを行いながら進む。
mirs.DBflagが0ならば通常どおりの動作を行い、1ならば各種仕掛け内に進入したか否かを常に監視しながら通路通行動作を行う。
void act_rota()
ロータリー通過動作を行う関数である。ロータリーの半径はあらかじめ決まっているので、右と左のモーターの速度はあらかじめ設定したものを使う。
左回りに旋回動作を行い、右の超音波センサーを常に監視し、道を発見したら位置補正を行い脱出を試みる。
void act_kobeya()
小部屋通過動作を行う関数である。小部屋へ侵入した場合、右端・中央・左端の3つの侵入方法があるため、どこから入ってきたのかを超音波センサーで判別する。
他にもタッチセンサー衝突時の補正動作も組み込んでいる。
void act_hakusen()
白線動作を行う関数である。DBとMG3との距離を一定に保ち、DBを正面に捉えられるように各種補正をかける。
また、途中で壁に引っかかってしまった場合の回避動作も組み込んでいる。
void act_key()
鍵と扉通過動作を行う関数である。超音波センサーで鍵の直前まで近づき、正面タッチセンサーが反応するまで前進するといった単純なものである。
電子コンパスの電源をドータボードから取ると、何故か赤外線センサーが反応しないので、今回は赤外線センサーは用いていない。
void act_DB()
DB読み取り動作を行う関数である。内部でinput_DB関数を実行し、画像処理を行った後、かえってきた値から最も優先度の高い番号を選びそれに対応する仕掛け通過プログラムに切り替える。
また、DB読み取りミスのたびに、少しずつ角度をずらすなどといったことも行っている。
5回失敗が続いたら数字認識の手法を少し変えて、10回失敗が続いたらさらに別の手法を用いるなどといったことも行っている。
void act_goal()
ゴール動作を行う関数である。前方の白線センサー反応後、しばらく前進してすべての白線センサーが反応した場合に行う。少し前進したり少しその場回転したりなど、多少の確認動作を行った後、
ゴールアピール動作を行い、プログラムを終了する。なお、ゴール動作は星のカー○ィのステージクリア曲に合わせて踊るといったものである。
void act_ikidomari()
行き止まり回避動作を行う関数である。全ての超音波センサーが壁を検知した場合に行う。180度回転した後、分かれ道(前方と右もしくは左が道)を探し、見つかったらそこを曲がり、180度回転して
行き止まりと判別する前の動作に戻る。
void print_state(int state)
linuxのターミナルに出力する文字の色を変えられるので、それを用いて現在の状態(通路通行・ロータリー通行など)を見やすく表示するといったことを行う。
競技会本番では無線環境が使えないため、デバッグ時のみ重宝する関数である。
double gettime()
OSが起動してからのクロック数をマイクロ秒単位まで取得する関数である。これをメインプログラムのwhile文の最初と最後につけ、その差を取ることにより、プログラムの1周に要する時間が分かる。
void ini()
各種変数・構造体の初期化を行う。構造体の初期化はmemset関数を用いている。
void ini_onoff_thread()
on/off IOセンサー監視スレッドの起動を行う。
void ini_thread()
超音波センサー監視スレッドと電子コンパス監視スレッドの起動を行う。
void select_state()
白線センサーの監視を行う。
シリアル通信系
int uss_get(int uss_num)
(標準)
uss_numで指定された番号の超音波センサーの値を返す。詳しくは標準プログラムのドキュメントを参照されたし
int set_serial_port(int fd, struct termios *oldtio)
(標準)
シリアル通信の初期設定を行う。ボーレート、非カノニカル通信、パリティエラーを無視、キャラクタブロック数の設定などを行う。
void close_serial_port(int fd, struct termios *oldtio) (標準)
set_serial_portで開いたポートを閉じる
int put_serial_char(int fd, char c)
(標準)
ファイルディスクリプタで指定されたデバイスに一文字転送
int put_serial_string(int fd, char *s)
(標準)
ファイルディスクリプタで指定されたデバイスに文字列を転送
int get_serial_char(int fd)
(標準)
ファイルディスクリプタで指定されたデバイスから一文字取得
int get_serial_char_motor(int fd)
ファイルディスクリプタで指定されたデバイスから一文字取得
int ave_uss(int num, int loop)
numで指定された番号の超音波センサーの値をloopで指定された数だけ取得し、その平均を取る。
現在は処理速度の関係で使用していない。
void *uss_thread()
超音波センサー監視スレッド
超音波センサー1〜3の値を一定周期でグローバル変数に代入する。sensor.ussflag1 sensor.ussflag2
sensor.ussflag3の値が
0の場合はそれに対応した超音波センサーは監視しない。そうすることにより、必要なセンサーの監視速度を向上させることができる。
void *onoff_thread()
on/off IOセンサーの値を一定周期でグローバル変数に代入する。なお、白線センサーに関しては、一瞬の反応でも見逃さないように、一回でも白線センサーが反応したら
リセットしない限りその値を保持するようにしている。
void ini_ds()
電子コンパスの初期設定を行う。 実際に送っているコマンドは * DM FB @21
w47_74_51_41 であるこのコマンドについては別ドキュメントにて説明してある。
他にも、シリアル通信の初期設定を行っている。
int ds_get()
電子コンパスに現在角度を読み取るための、コマンドを送り、read関数でその値を読み取る。その値(016〜E0F16)を実際の角度(01-127〜35910)に変換してその値を返す。
なお、電子コンパスから送られてくる値は16進表記の文字であるので、atoi関数を用いれば容易に変換できるが、処理を重くしたくないので変換条件をつくり変換した。
void *ds_thread()
電子コンパスの値を一定周期でグローバル変数に代入する。そのままの値は-127〜359であるため、359度の次に0度となってしまい、条件式を作る際に非常に都合が悪い。
そこで、1つ前の角度と現在の角度を比較し、その差が180度以上あった場合、0と359をまたいだと考え、カウントを±1(0→359と359→0の場合)し、
現在角度(-127〜359)+360×カウント数(-∞〜∞の整数)
という風にし、現在の周回数も数えられるような仕様にした。1ループの間に角度が物理的に180度変化した場合にはこの方法では誤認識をしてしまうが、1ループが高速なので、
物理的にあり得ないと想定し、この方法を用いることにした。
void ds_conv()
電子コンパスの値をある規則に従い修正する。
電子コンパスの値は、自動補正機能を用いても、若干実際の角度と違ってしまう。そこで、45度刻みに実測値と理論値を比較し、Excelを用いて45度の間を補間する。
そうすることにより、かなり正確な角度を取得することができる。
駆動系
void send_byte(int16_t request)
モータコントロールボードに8bitの情報を送信する。
void read_byte()
モータコントロールボードから8bitの情報を受信する。
void motor_right(char dir,int8_t speed)
右タイヤの速度と方向を指定する。正転の場合はdir=’f’ 逆転の場合はdir=’r’停止の場合はdir=’b’である。
速度はspeed(-127〜127)の範囲で設定できる。
void motor_left(char dir,int8_t speed)
左タイヤの速度と方向を指定する。パラメータはmotor_rightと同じ。
int lolita(char dir)
左右タイヤのロータリエンコーダの値を返り値として返す。
void motor_forward(int speed)
speed(-127〜127)で指定された速度で前進をする。電子コンパスの値から、なるべく直進できるように補正も行っている。
void motor_back(int speed)
speed(-127〜127)で指定された速度で後退する。こちらは直進補正は行っていない。
void motor_stop2()
モーターを停止させる。それ以上でもそれ以下でもない
void motor_rotation(int speed,int muki)
speed(-127〜127)で指定された速度で、mukiがRIGHT(マクロで定義)なら右その場回転、LEFTなら左その場回転する。
int motor_circle(int speed,int rad,int muki,int r,int l)
int turn_right(int speed,int rotang,int err)
speed(-127〜127)で指定された速度で、rotangで指定された角度(deg)だけ右その場回転する。なお、誤差は±err度である。
int turn_left(int speed,int rotang,int err)
speed(-127〜127)で指定された速度で、rotangで指定された角度(deg)だけ左その場回転する。なお、誤差は±err度である。
int motor_forward_dis(int speed,int rotdis,int err)
speed(-127〜127)で指定された速度で、rotdisで指定された距離(mm)だけ前進する。なお、誤差は±err度である。
int motor_back_dis(int speed,int rotdis,int err)
speed(-127〜127)で指定された速度で、rotdisで指定された距離(mm)だけ後退する。なお、誤差は±err度である。
int turn_collect(int speed,int err)
mirs.directionで指定されている角度と、現在の角度を比較し、最短ルートでmirs.directionの角度になるようにその場回転する。
mirs.directionの中は、現在進むべき角度を代入することになっているので、つまりこれは進むべき角度からズレた場合の補正動作になる。
int touch_collect(int speed,int muki,int err)
主に、左右のタッチセンサー反応時に用いる。壁との角度をあえて一定にし、そこから一定距離さがり、元の角度に戻ることにより、どの角度でタッチセンサーが反応しても、
同様に道幅の中央付近まで下がることができ、その後、再び左右のタッチセンサーが押されることが少なくなる。これにより、少しでも競技クリア速度を上げようという考えである。
画像処理系
int input_DB(int *num1,char *ps1,int *num2,char *ps2,char *col_dir,int
otsuflag,int smoothflag)
num1,num2にそれぞれ認識した数字を、ps1,ps2にnum1,num2の数字の相対位置(どちらが右でどちらが左か)を代入する。
col_dirには現在DBから左右どちらに角度補正をかければいいかを代入する。
otsuflag(0 or 1)を1にすると大津の手法を用いた数字認識を行い、smoothflag(0
or 1)を1にすると画像の平滑化を行うようになる。
この2つのオプションの使い分けで、より精度の高い数字認識を目指した。大津の手法、画像の平滑化については、画像処理のドキュメントにて詳しく説明しているので参照されたし。
void img_update(char *str, int width, int height)
(標準)
標準プログラムをそのまま用いている。 strで指定されたファイル名でwidth×heightのサイズでキャプチャを行う。実際にはsystem関数でuvccaptureを実行している。
opencvにもキャプチャ関数があるが、処理落ちがひどくて現在のスペックでは使い物にならないのでこちらを用いている。
void threshold(IplImage *src,IplImage *dst,int otsuflag)
画像の2値化を行っている。srcに入力画像、dstに出力画像領域を指定し、otsuflagにはinput_DBで指定したotsuflagの値を入れる。
int labeling(IplImage *src,IplImage *dst1,IplImage *dst2,char *collect,int
*centerpos)
画像のラベリングを行っている。srcに入力画像、dst1に出力画像領域その1、dst2に出力画像領域その2を指定し、collectには左右どちらに補正をかければいいかが代入される。
centerposにはDB中央のしきりの座標が代入される。
void gettrim(IplImage *src,CvRect *rect)
srcには入力画像を指定し、rectにはsrcの数字部を内包する最小矩形の矩形情報が代入される。
void triming(IplImage *src,IplImage *dst,CvRect rect)
srcのrectで指定された矩形領域を切り取り、dstに出力する。
void noisecut(IplImage *src,IplImage *dst)
画像のDBより上の部分を真っ白な領域にする。観客などを消し去る効果がある。
void select_number(IplImage *src1,IplImage *src2,int *num1,int *num2)
src1で指定された画像から、アルゴリズムに従い、-127〜9までの値を決定し、num1、num2に代入される。
デバッグ・パフォーマンス系
void beep(float midi_no,int lengh)
midi_noでmidiの音階を指定し、lenghでその音の長さを指定してビープ音を鳴らす。
void music_start(char fname[100],int beep_s)
fnameで指定されたパスのファイル(.bp)を用いて、beep_sのテンポでメロディを演奏する。
void ini_bgm()
bgmtestの実行 引数は0にしてあるので音はならない。
void bgm_change(int num)
numで指定された番号を引数としてbgmtestをバックグラウンドで実行する。番号に対応したメロディが流れる。
また、.bpファイルの登録は、csvファイルにパスとテンポを記述するだけでできるようにしてある。これにより、Excelを用いることができるので登録が簡単になった。
void *bgm_thread(int num)
BGMを鳴らすスレッド
どうしても音が途中で切れてしまうのであまり使えない。
6 変数・構造体一覧
変数名 |
概要 |
int ksmdis |
小部屋通過時のみに用いる。小部屋内の直進距離が入る。小部屋内で曲がるとリセットされる |
int
ksmstddis |
小部屋進入時のロータリーエンコーダーの値が入る。小部屋進入時の値を0にするための基準値 定数 |
int
directDB |
画像認識失敗後、左右どちらに角度補正すべきかが入る。 |
int
stdrot_r |
プログラム開始時の右のロータリーエンコーダーの値が入る。定数 |
int
stdrot_l |
プログラム開始時の左のロータリーエンコーダーの値が入る。定数 |
int
motor_err_cnt |
モーターとの通信エラーのカウント数が入る。 |
float
stddis |
各種パラメーターから計算された、ロータリーエンコーダー1カウントで進む距離(mm)が入る。定数 |
構造体名 |
メンバー名 |
概要 |
sensor_t |
int
uss1,uss2,uss3 |
超音波センサー1,2,3の値が代入される。 |
int
ts1,ts2,ts3 |
タッチセンサー1,2,3の値が代入される。 |
|
int
ws1,ws2,ws3 |
白線センサー1,2,3の値が代入される。 |
|
int
ussflag1,ussflag2,ussflag3 |
これらを0にするとそれに対応した超音波センサーの起動を停止する。 |
|
mirs_t |
int state |
この値からact_normalなどといった関数のどれを用いるかが決まる |
int
next_state |
例外処理が起きたとき、例外処理終了後にstateをどの値にするかを入れる。 退避用 |
|
int
state_old |
例外処理が起きたとき、例外処理終了後にstateをどの値にするかを入れる。 退避用 |
|
int
next_dir |
数字認識成功した場合に代入される。左右どちらに曲がればいいかが入る。 |
|
int process |
act_ から始まる関数内の現在の状態(どの段階まで進んだか)が入る |
|
int
process_old |
例外処理が起きたとき、例外処理終了後にprocessをどの値にするかを入れる。 退避用 |
|
int
process_load |
processと同じ用途だが、これはact_normal()専用 |
|
int
process_load_old |
act_normal()実行時に例外動作が起きたとき、例外処理終了後にprocess_loadをどの値にするかを入れる。退避用 |
|
int
process_next |
例外処理が起きたとき、例外処理終了後にprocessをどの値にするかを入れる。 退避用 |
|
int
process_goal |
processと同じ用途だが、これはact_goal()専用。 |
|
int
process_white |
processと同じ用途だが、これはact_hakusen()専用。 |
|
int DBflag |
数字認識成功後、現在仕掛け前の通路を通行している場合は0 仕掛けに既に侵入している場合は1が入る。 |
|
int
rota_deguti |
ロータリー通過時のみ使う。ロータリーの出口が4つのうちどこにあるかの情報が入る |
|
int
angle_now |
電子コンパスの値に360×周回数を足したものが入る。 |
|
int
rot_r,rot_l |
rot_rには右の、rot_lには左のロータリーエンコーダーから算出した移動距離が入る。 |
|
int
rot_r_old,rot_l_old |
rot_r、rot_lの値を一時的に保持したい場合にrot_r、rot_lの内容を代入する。 |
|
int
direction |
現在進むべき角度(90度刻み)が入る。 |
|
num_t |
int num |
数字認識で取得した数字(0〜9)が入る。数字認識失敗時には -1 が入る |
int pos |
数字認識で取得した数字の相対位置('R' or
'L')が入る。 |
|
int sksr |
取得した数字の競技での優先度の数字が入る |
7 まとめ
実際このようにドキュメントにまとめると、使っていない変数や関数があることに気づき、非常に無駄の多いコーディングであったと思った。
今度作るときはもう少し無駄を省いた綺麗なプログラムを作りたい。
関連文書 |