後輩くん向けメモ
複雑なifはステート化する
変更前
なんかセンサーで障害物を回避したいとか言ってやってるが、
奇々怪々な挙動をして、一体どのif文が反応しているかわからない...
優先順位もよくわからない
- 条件と行いたいことが混ざっていて見通しが悪い
- その場限りの条件が量産される
- 自分で条件が把握しきれなくなっている
//一番上の条件が優先される
if(Sensor1 < 10 && Sensor2 < 10 && Sensor3 < 10)
{
MotorMode(0);
LMotorNextStep(10);
RMotorNextStep(10);
}else if(Sensor1 < 10 && Sensor2 < 10)
{
if(Sensor1 < 5)
{
MotorMode(0);
LMotorNextStep(10);
RMotorNextStep(20);
}else{
MotorMode(0);
LMotorNextStep(20);
RMotorNextStep(10);
}
}else if(Sensor2 < 10 && Sensor3 < 10)
if(Sensor3 < 5)
{
MotorMode(0);
LMotorNextStep(10);
RMotorNextStep(20);
}else{
MotorMode(0);
LMotorNextStep(20);
RMotorNextStep(10);
}else{
...
変更後
判定と行動を分ける。
- 条件と、行いたい行動が分離して見通しが良くなる。
- また、現在の状態を知るために1つの変数を見るだけで良くなる。
- 前回の判定結果との差を使えるようになる。
#define MODE_AHEAD 0
#define MODE_LEFT 1
#define MODE_RIGHT 2
#define MODE_BACK 3
static int dir = MODE_AHEAD; //どれにも当てはまらなかった場合
static int old_dir = MODE_AHEAD; //前回の条件を記録する
//-----------判定---------------
if(Sensor1 < 10 && Sensor2 < 10 && Sensor3 < 10)
{
dir = MODE_BACK;
}
if(Sensor1 < 10 && Sensor2 < 10)
{
dir = MODE_LEFT;
}
if(Sensor1 < 5 && Sensor2 < 10)
{
dir = MODE_RIGHT;
}
//直前の挙動を判定に使えるようになる
if(Sensor1 < 5 && Sensor2 < 10 && old_dir == MODE_BACK)
{
dir = MODE_LEFT;
}
...
//一番最後に代入された条件が実行される
//→先に行った条件の緩い判定を、より厳しい判定で上書きできる
//-----------行動---------------
if(dir == MODE_AHED)
{
MotorMode(2);
LMotorNextStep(10);
RMotorNextStep(10);
}
if(dir == MODE_BACK)
{
MotorMode(0);
LMotorNextStep(10);
RMotorNextStep(10);
}
...
//-----------報告---------------
//変化点を報告できるようになる
if(dir != old_dir)
{
printf("Mode: %d → %d\n",old_dir,dir);
}
old_dir = dir; //更新
変更後2
2017/09/10
頂いたコメントより。
指導の都合上、ifからの変形を意図していたためifのままでしたが、switch使ったほうがスッキリしますね。
break;
を忘れると大事故になるので注意
...
//-----------行動---------------
switch (dir) {
case MODE_AHED:
MotorMode(2);
LMotorNextStep(10);
RMotorNextStep(10);
break;
case MODE_BACK:
MotorMode(0);
LMotorNextStep(10);
RMotorNextStep(10);
break;
...
}
ループは小さく回す
関数が大回りしている。
結果グローバル変数祭りや、static変数祭りになりがちになる。
変更前
int mode = 0;
void setup()
{
...
}
void loop()
{
//順序だってやる処理
switch(mode)
{
case 0:
mode0();
break;
case 1:
mode1();
break;
case 2:
mode2();
break;
}
}
//暫く掛かる(かなりの回数のループを必要とする)処理
void mode0()
{
...
if(...)
{
mode = 1;
}
...
}
変更後
void setup()
{
...
}
void loop()
{
//順序どおり書ける
mode0();
mode1();
mode2();
}
//処理が終わるまでローカルスコープを抜けないため、
//ローカル変数を有効に使えるようになる。使い回しが効きやすくなる
void mode0()
{
//ここで初期化処理ができるようになる
int cnt = 0;
//ループの内側だけ考えれば良くなる。
while(1)
{
...
if(...)
break;
}
//終了処理ができるようになる。
}
グローバル変数は使うファイルにまとめる
変更前
#include "global.c"
#include "mode0.c"
//modeやdeltaはここでしか使わない
void setup()
{
mode=0;
delta = 0.552;
}
void loop()
{
if(mode == 0)
mode0();
if(...)
mode++;
}
//somevarはここでしか使わない
void mode0()
{
somevar+=2.2
...
}
int i; //←!?
int mode=0;
float delta = 0.552;
float somevar = 1.3;
//その他プログラム中全てのグローバル変数数十個がここに集結している
//なんでや
変更後
※そもそもグローバル変数使わないに越したことはないですが。
2017/09/10
コメントより、ファイルスコープに収めるためにstatic付与したほうが安全ということで修正。
こうすることで、別のファイルから別のファイルのグローバル変数をうっかり触ることがなくなります。
#include "mode0.c"
static int mode=0;
static float delta = 0.552;
void setup()
{
mode=0;
delta = 0.552;
}
void loop()
{
if(mode == 0)
mode0();
if(...)
mode++;
}
static float somevar = 1.3;
void mode0()
{
somevar+=2.2
...
}
ピンには名前をつける
変更前
本人でもわからなくなります。
void setup()
{
pinMode(0,OUTPUT);
pinMode(1,OUTPUT);
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,INPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
}
void loop()
{
digitalWrite(3,HIGH);
for(int i=0;i<5;i++)
{
digitalWrite(2,HIGH);
digitalWrite(5,HIGH);
delay(1);
digitalWrite(2,LOW);
digitalWrite(5,LOW);
delay(1);
}
if(digitalRead(4)==LOW)
{
digitalWrite(6,HIGH);
}else{
digitalWrite(6,LOW);
}
digitalWrite(3,LOW);
delay(50);
}
変更後
定数で名前つけましょう。C言語風にdefine使ってもいいですが、
事故防止にはconstがおすすめです。
const int running_led = 3;
const int sensor_led = 6;
const int sensor_in = 4;
const int stepping_motor_L = 2;
const int stepping_motor_R = 5;
const int comm1 = 0;
const int comm2 = 1;
void setup()
{
pinMode(comm1,OUTPUT);
pinMode(comm2,OUTPUT);
pinMode(stepping_motor_L,OUTPUT);
pinMode(running_led,OUTPUT);
pinMode(sensor_in,INPUT);
pinMode(stepping_motor_R ,OUTPUT);
pinMode(sensor_led,OUTPUT);
}
void loop()
{
digitalWrite(running_led,HIGH);
for(int i=0;i<5;i++)
{
digitalWrite(stepping_motor_L ,HIGH);
digitalWrite(stepping_motor_R ,HIGH);
delay(1);
digitalWrite(stepping_motor_L ,LOW);
digitalWrite(stepping_motor_R ,LOW);
delay(1);
}
if(digitalRead(sensor_in)==LOW)
{
digitalWrite(sensor_led,HIGH);
}else{
digitalWrite(sensor_led,LOW);
}
digitalWrite(running_led,LOW);
delay(50);
}
変更後2
2017/09/10
enumを使うと簡素になりますとご指摘を頂いたので。たしかにその通りです。
よりシンプルに書けて良いですね。
(恥ずかしながらこういったシンプルなenumの使い方を知りませんでした)
ただし、Arduino等ではconst intの列挙が一般的のようです。
enum {
running_led = 3,
sensor_led = 6,
sensor_in = 4,
stepping_motor_L = 2,
stepping_motor_R = 5,
comm1 = 0,
comm2 = 1,
};
void setup()
{
pinMode(comm1,OUTPUT);
pinMode(comm2,OUTPUT);
pinMode(stepping_motor_L,OUTPUT);
pinMode(running_led,OUTPUT);
pinMode(sensor_in,INPUT);
pinMode(stepping_motor_R ,OUTPUT);
pinMode(sensor_led,OUTPUT);
}
void loop()
{
digitalWrite(running_led,HIGH);
for(int i=0;i<5;i++)
{
digitalWrite(stepping_motor_L ,HIGH);
digitalWrite(stepping_motor_R ,HIGH);
delay(1);
digitalWrite(stepping_motor_L ,LOW);
digitalWrite(stepping_motor_R ,LOW);
delay(1);
}
if(digitalRead(sensor_in)==LOW)
{
digitalWrite(sensor_led,HIGH);
}else{
digitalWrite(sensor_led,LOW);
}
digitalWrite(running_led,LOW);
delay(50);
}
デバイスは抽象化する
変更前
確かに動くんですけど、後々仕様変更する時に死ぬ思いします。
モータードライバを変えるとか、速度を変えるとか。
if(dir == MODE_AHED)
{
digitalWrite(stepping_motor_L_dir ,HIGH);
digitalWrite(stepping_motor_R_dir ,LOW);
digitalWrite(stepping_motor_L_boostmode ,LOW);
digitalWrite(stepping_motor_R_boostmode ,LOW);
for(int i=0;i<500;i++)
{
digitalWrite(stepping_motor_L ,HIGH);
digitalWrite(stepping_motor_R ,HIGH);
delay(1);
digitalWrite(stepping_motor_L ,LOW);
digitalWrite(stepping_motor_R ,LOW);
delay(1);
}
}
if(dir == MODE_BACK)
{
digitalWrite(stepping_motor_L_dir ,HIGH);
digitalWrite(stepping_motor_R_dir ,HIGH);
digitalWrite(stepping_motor_L_boostmode ,LOW);
digitalWrite(stepping_motor_R_boostmode ,LOW);
for(int i=0;i<500;i++)
{
digitalWrite(stepping_motor_L ,HIGH);
digitalWrite(stepping_motor_R ,HIGH);
delay(1);
digitalWrite(stepping_motor_L ,LOW);
digitalWrite(stepping_motor_R ,LOW);
delay(1);
}
}
変更後
複雑な処理、何度もやる処理は、関数に隠しましょう。
可能であれば、見ただけで「なにをしているのか」が分かる名前や単位に分解できると、コメントがなくても理解しやすいプログラムになります。
if(dir == MODE_AHED)
{
MotorMode(2);//動作を分解し、関数化しておく。関数の関数の関数とかもよくある。
LMotorNextStep(10);
RMotorNextStep(10);
}
if(dir == MODE_BACK)
{
MotorMode(0);
LMotorNextStep(10);
RMotorNextStep(10);
}
変更後2
抽象化しておくと、PCのC言語環境などでシミュレーションするのも楽になるし、コードの量も減る。
if(dir == MODE_AHED)
{
robo_move(move_ahed,10);
}
if(dir == MODE_BACK)
{
robo_move(move_back,10);
}
長期間存在する変数を直接触らない&複数のことを1箇所でやろうとしない
モーターの動いた距離を計算したいらしいといった場合。
変更前
- 距離変数を直接足したり引いたりしている
- そのうち妙な操作をし始めたり、別の場所で参照を始めるだろう
- 式中の定数に名前を振っていない
- 直接の目的外のことを同じ関数に書いている(行動と移動距離の測計算)
...
//-----------行動---------------
switch (dir) {
case MODE_AHED:
robo_move(move_ahed,10);
//変数を直接操作している
step_l += 10;
step_r += 10;
break;
case MODE_BACK:
robo_move(move_back,10);
//もちろん何箇所もある
step_l -= 10;
step_r -= 10;
break;
...
}
...
//-----------距離計算---------------
//変数を直接操作している&定数に名前がない&邪魔
len_l = (step_l/600)*16.53;
len_r = (step_r/600)*16.53;
delta_theta = atan(...,...)
delta_x = len_x*cos(...);
delta_y = len_x*sin(...);
//ほしいのはこれら
x += delta_x;
y += delta_y;
t += delta_theta;
step_l = 0;
step_r = 0;
...
変更後
- 分離した
- 変数には直接触らず、操作用の関数を通して触る
- 計算式や、記録したい情報の形式の変更時、デバッグのメッセージを仕込むなどと言ったときに便利になる
...
//-----------行動---------------
switch (dir) {
case MODE_AHED:
robo_move(move_ahed,10);
//抽象化した
add_step(10,10);
break;
case MODE_BACK:
robo_move(move_back,10);
add_step(-10,-10);
break;
...
}
...
//読み出しもシンプルになる
float x,y,t;
get_step_len(&x,&y,&t);
...
//移動と直接関係ないのでファイルごと分離できる
const float wheel_size = 16.53;
const float cycle_step = 600;
static int step_l=0;
static int step_r=0;
static float move_x=0;
static float move_y=0;
static float move_theta=0;
void reset_move()
{
move_x = 0;
move_y = 0;
move_theta = 0;
}
void reset_step()
{
step_l = 0;
step_r = 0;
}
void add_step(int l,int r)
{
step_l += 10;
step_r += 10;
}
void get_step_len(float *x,float *y,float *t)
{
len_l = (step_l/cycle_step)*wheel_size;
len_r = (step_r/cycle_step)*wheel_size;
delta_theta = atan(...,...)
delta_x = len_x*cos(...);
delta_y = len_x*sin(...);
move_x += delta_x;
move_y += delta_y;
move_theta += delta_theta;
*x = move_x;
*y = move_y;
*t = move_theta;
reset_step();
}
変更後2
クラス化
walkCalc log;
...
//-----------行動---------------
switch (dir) {
case MODE_AHED:
robo_move(move_ahed,10);
log.add_step(10,10);
break;
case MODE_BACK:
robo_move(move_back,10);
log.add_step(-10,-10);
break;
...
}
...
//読み出しもシンプルになる
float x,y,t;
log.get_step_len(x,y,t);
...
class walkCalc
{
private:
const float wheel_size = 16.53;
const float cycle_step = 600;
int step_l=0;
int step_r=0;
float move_x=0;
float move_y=0;
float move_theta=0;
public:
void reset_move()
{
move_x = 0;
move_y = 0;
move_theta = 0;
}
void reset_step()
{
step_l = 0;
step_r = 0;
}
void add_step(int l,int r)
{
step_l += 10;
step_r += 10;
}
//参照渡し
void get_step_len(float &x,float &y,float &t)
{
len_l = (step_l/cycle_step)*wheel_size;
len_r = (step_r/cycle_step)*wheel_size;
delta_theta = atan(...,...)
delta_x = len_x*cos(...);
delta_y = len_x*sin(...);
move_x += delta_x;
move_y += delta_y;
move_theta += delta_theta;
x = move_x;
y = move_y;
t = move_theta;
reset_step();
}
}
参照渡しについては以下を参照
C++ 値渡し、ポインタ渡し、参照渡しを使い分けよう
http://qiita.com/agate_pris/items/05948b7d33f3e88b8967