2013/12/29

アーム根元ヨー軸のポテンショメータ


suzutoです。

アームの回転板のポテンショメータに、今まで使っていたGreen Pot以外のポテンショメータが使えないか探してました。場所が場所なのでキー溝タイプのものを探したのですが、ほとんど見つかりませんでした。キー溝タイプのポテンショってあまりないんですね、勉強不足でした。ということで、結局同じものを使うことにしました。取り付け方もとりあえず同じにします。もう少し機体の中身ができてきたら考え直すかもしれないです。



変えたところといえば、ポテンショ周りの板を減らして回転板の手入れをしやすい(と思う)感じにしたくらいです。ちなみに、夏に作り直した俯瞰カメラの昇降部分はそのまま使います。
今後は回転板の回転方法やクローラについて考えていこうと思います。

2013/12/27

慣性モーメントの計算の改善案

既に決まったルーチンをコンピュータにやらせるのはいい考えですね! ただ、ソースコードが以下の点で保守がしづらいと思われますので、改善案を示しておきます。

1. グローバル変数が多数の関数内で参照 & 書き換えがされていて、修正が容易でないこと。
2.  同じ変数について、ifでの分岐があちこちに散在していてバグの元となること。
3. 「3.14」の精度が足りない場合がある。それ以前にプログラム中に出てくる定数はマジックナンバーと呼ばれ、修正が大変になるので事前に定義しておく。

#include <stdio.h>

#define _USE_MATH_DEFINES
#include <math.h>

double chouhoukei(double* ref_m, double ro);
double nagaibou(double* ref_m, double ro);
double enban(double* ref_m, double ro);
double enchuu(double* ref_m, double ro);
double entou(double* ref_m, double ro);
double kyuu(double* ref_m, double ro);
double kyuukaku(double* ref_m, double ro);

static const double g_ro[3] = { 2.7 / 1000000, 7.9 / 1000000, 1.19 / 1000000 };
static const double (*f[7])(double*, double) = { chouhoukei, nagaibou, enban, enchuu, entou, kyuu, kyuukaku };

int main(void) {
   int i;
   float t0 = 0;

   printf("任意の軸(機力の教科書p251の図参照)まわりの慣性モーメントJ[kgcm^2]\n");

   for(i = 1; ; i++) {
      // 使う変数の領域を考えて極量スコープを小さく
      int u, n;
      double e, m, ro, t;

      printf("部材を番号で選んでください\n0.アルミ,1.ステンレス,2.アクリル,3.その他(密度既知)\n4.その他(質量既知),5.終了\n");
      scanf("%d", &u);

      // 密度既知の場合
      if(u >= 0 && u <= 2) {
         ro = g_ro[u];
      }
      else if(u == 3) {
         ro = 0;
         printf("密度[kg/m^3]を入力してください\n");
         scanf("%lf", ro);
         ro /= 1000000000;
      }
      else if(u == 5) {
         break;
      }

      printf("部品%dの形を番号で選んでください.\n1.長方形板,2.長い棒,3.薄い円盤,4.円柱,5.円筒形\n6.球,7.球殻,8その他(慣性モーメント既知)\n", i);
      scanf("%d", &n);

      // 質量既知の場合
      if(u == 4) {
         printf("質量[g]を入力してください\n");
         scanf("%lf", &m);
         m /= 1000;
         ro = 0;
      }
      else {
         m = 0;
      }

      // 慣性モーメントが既知の場合
      if(n == 8) {
         printf("\n部品%dの慣性モーメント[kgmm^2]を入力してください\n\n", i);
         scanf("%lf", &t);
      }
      // 想定外の値が入力されたらエラーを表示
      else if(n < 0 || n > 8) {
         // printf("nは1から8の範囲で入力して下さい\n");
         continue;
      }
      // elseを使う場面
      else {
         printf("重心の回転軸からの距離[mm]を入力してください\n");
         scanf("%lf", &e);

         // 同じ処理はまとめる
         t = (*f[n - 1])(&m, ro) + e * e * m;

         printf("\n部品%dの質量=%lf[kg]\n", i, m);
         printf("部品%dのJ=%lf[kgcm^2]\n\n", i, t / 100);
      }

      t0 += t / 100;
   }

   printf("\n全体の慣性モーメント\nJ=%lf[kgcm^2]", t0);

   return 0; // 正常の場合はmain関数は0(EXIT_SUCCESS)を返す
}

double chouhoukei(double* ref_m, double ro) {
   int p;
   double a, b, tt, m;

   printf("回転軸を選んでください\n0.x  1.y  2.z\n");
   scanf("%d", &p);

   printf("横の長さ[mm]を入力してください\n");
   scanf("%lf", &b);

   printf("縦の長さ[mm]を入力してくだいさい\n");
   scanf("%lf", &a);

   m = *ref_m;

   if(m == 0) {
      printf("厚さ[mm]を入力してください\n");
      scanf("%lf", &tt);

      m = ro * (a * b * tt);
      *ref_m = m;
   }

   switch(p) {
   case 0:
      return a * a * m / 12;
   case 1:
      return b * b * m / 12;
   case 2:
      return ((a * a + b * b) * m) / 12;
   }

   return 0;
}

double nagaibou(double* ref_m, double ro) {
   double l, tt, m;

   printf("長さ[mm]を入力してください\n");
   scanf("%lf", &l);

   m = *ref_m;
   if(m == 0) {
      printf("直径[mm]を入力してください\n");
      scanf("%lf", &tt);

      m = ro * (M_PI * tt * tt / 4) * l;
      *ref_m = m;
   }

   return m * l * l / 12;
}

double enban(double* ref_m, double ro) {
   double r, tt, m;
   int p;

   printf("回転軸を選んでください\n0.x  1.y  2.z\n");
   scanf("%d", &p);

   printf("半径[mm]を入力してください\n");
   scanf("%lf", &r);

   m = *ref_m;
   if(m == 0) {
      printf("厚さ[mm]を入力してください\n");
      scanf("%lf", &tt);

      *ref_m = ro * M_PI * r * r * tt;
      m = *ref_m;
   }

   switch(p) {
   case 0:
      return r * r * m / 4;
   case 1:
      return r * r * m / 4;
   case 2:
      return r * r * m / 2;
   }

   return 0;
}

double enchuu(double* ref_m, double ro) {
   int p;
   double r, l, m;
   
   printf("回転軸を選んでください\n0.x  1.y  2.z\n");
   scanf("%d", &p);

   printf("半径[mm]を入力してください\n");
   scanf("%lf", &r);

   printf("長さ[mm]を入力してくだいさい\n");
   scanf("%lf", &l);

   m = *ref_m;
   if(m == 0) {
      m = ro * M_PI * r * r * l;
      *ref_m = m;
   }

   switch(p) {
   case 0:
      return (r * r / 2 + l * l / 12) * m;
   case 1:
      return (r * r / 2 + l * l / 12) * m;
   case 2:
      return r * r * m / 2;
   }

   return 0;
}

double entou(double* ref_m, double ro) {
   int p;
   double r, l, tt, m;

   printf("回転軸を選んでください\n0.x  1.y  2.z\n");
   scanf("%d", &p);
   printf("半径[mm]を入力してください\n");
   scanf("%lf", &r);

   // これ忘れているんじゃないか?
   printf("長さ[mm]を入力してくだいさい\n");
   scanf("%lf", &l);
   // ここまで

   m = *ref_m;
   if(m == 0) {
      printf("筒の厚さ[mm]を入力してください\n");
      scanf("%lf", &tt);

      m = ro * M_PI * r * r * l - M_PI * (r - tt) * (r - tt) * l;
      *ref_m = m;
   }

   switch(p) {
   case 0:
      return (r * r / 2 + l * l / 12) * m;
   case 1:
      return (r * r / 2 + l * l / 12) * m;
   case 2:
      return r * r * m;
   }

   return 0;
}

double kyuu(double* ref_m, double ro) {
   double r, m;
   
   printf("半径[mm]を入力してください\n");
   scanf("%lf", &r);

   m = *ref_m;
   if(m == 0) {
      m = ro * 4 * M_PI * r * r * r / 3;
      *ref_m = m;
   }

   return 2 * m * r * r / 5;
}

double kyuukaku(double* ref_m, double ro) {
   double r, tt, m;

   printf("半径[mm]を入力してください\n");
   scanf("%lf", &r);

   m = *ref_m;
   if(m == 0) {
      printf("核の厚さ[mm]を入力してください\n");
      scanf("%lf", &tt);

      m = ro * 4 * M_PI * r * r * r / 3 - 4 * M_PI * (r - tt) * (r - tt) * (r - tt) / 3;
      *ref_m = m;
   }

   return 2 * r * r * m / 3;
}

2013/12/26

慣性モーメントの計算してみた

 昨日作ったプログラムを手計算ZERO目指してあれこれ継ぎ足して下の表を片手に計算をやらせてみました。
 計算結果があってるかどうかは気にしないとして() ,いつもなら一部品計算して1時間の休憩をしてをやって最終的に「明日になったら頑張る」って投げ出していたところですが10分位でやりたい部分すべての計算を終わらせることができました!!計算があってればですけどね。
 まだおおまかな設計すら途中ですが・・・とりあえず頑張ります。
 絶対どこか計算間違えてる気がする。
 #include<stdio.h>
float chouhoukei(void);
float nagaibou(void);
float enban(void);
float enchuu(void);
float entou(void);
float kyuu(void);
float kyuukaku(void);
float sonota(void);
float situryou(void);
float a,b,l,r,m,j,d[3],ro[4]={2.7/1000000,7.9/1000000,1.19/1000000} ,v,tt;
int i,p,u;
void main(void)
{
int n,s;
float t,e,k,t0=0;
printf("任意の軸(機力の教科書p251の図参照)まわりの慣性モーメントJ[kgcm^2]\n");
i=1,u=0;
while(u!=5){
printf("部材を番号で選んでください\n0.アルミ,1.ステンレス,2.アクリル,3.その他(密度既知)\n4.その他(質量既知),5.終了\n");
scanf("%d",&u);
if(u!=5){
if(u==3){
printf("密度[kg/m^3]を入力してください\n");
scanf("%f",&ro[3]);
ro[3]=ro[3]/1000000000;
}
printf("部品%dの形を番号で選んでください.\n1.長方形板,2.長い棒,3.薄い円盤,4.円柱,5.円筒形\n6.球,7.球殻,8その他(慣性モーメント既知)\n",i);
scanf("%d",&n);
if(u==4){
printf("質量[g]を入力してください\n");
scanf("%f",&m);
m=m/1000;
}
if(n==8){
t=sonota();
}
if(n!=8){
printf("重心の回転軸からの距離[mm]を入力してください\n");
scanf("%f",&e);
if(n==1){
k=chouhoukei();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}

if(n==2){
k=nagaibou();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}

if(n==3){
k=enban();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}

if(n==4){
k=enchuu();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}

if(n==5){
k=entou();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}

if(n==6){
k=kyuu();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}

if(n==7){
k=kyuukaku();
t=k+e*e*m;
printf("\n部品%dの質量=%f[kg]\n",i,m);
printf("部品%dのJ=%f[kgcm^2]\n\n",i,t/100);
}
}
t0+=t/100;
i++;
}
}
printf("\n全体の慣性モーメント\nJ=%f[kgcm^2]",t0);
}

float chouhoukei(void){
printf("回転軸を選んでください\n0.x  1.y  2.z\n");
scanf("%d",&p);
printf("横の長さ[mm]を入力してください\n");
scanf("%f",&b);
printf("縦の長さ[mm]を入力してくだいさい\n");
scanf("%f",&a);
if(u!=4){
printf("厚さ[mm]を入力してください\n");
scanf("%f",&tt);
v=a*b*tt;
m=situryou();
}
d[0]=a*a*m/12;
d[1]=b*b*m/12;
d[2]=((a*a+b*b)*m)/12;
j=d[p];
return j;
}

float nagaibou(void){
printf("長さ[mm]を入力してください\n");
scanf("%f",&l);
if(u!=4){
printf("直径[mm]を入力してください\n");
scanf("%f",&tt);
v=(3.14*tt*tt/4)*l;
m=situryou();
}
j=m*l*l/12;
return j;
}

float enban(void){
printf("回転軸を選んでください\n0.x  1.y  2.z\n");
scanf("%d",&p);
printf("半径[mm]を入力してください\n");
scanf("%f",&r);
if(u!=4){
printf("厚さ[mm]を入力してください\n");
scanf("%f",&tt);
v=3.14*r*r*tt;
m=situryou();
}
d[0]=r*r*m/4;
d[1]=r*r*m/4;
d[2]=r*r*m/2;
j=d[p];
return j;
}

float enchuu(void){
printf("回転軸を選んでください\n0.x  1.y  2.z\n");
scanf("%d",&p);
printf("半径[mm]を入力してください\n");
scanf("%f",&r);
printf("長さ[mm]を入力してくだいさい\n");
scanf("%f",&l);
if(u!=4){
v=3.14*r*r*l;
m=situryou();
}
d[0]=(r*r/2+l*l/12)*m;
d[1]=(r*r/2+l*l/12)*m;
d[2]=r*r*m/2;
j=d[p];
return j;
}

float entou(void){
printf("回転軸を選んでください\n0.x  1.y  2.z\n");
scanf("%d",&p);
printf("半径[mm]を入力してください\n");
scanf("%f",&r);
if(u!=4){
printf("筒の厚さ[mm]を入力してください\n");
scanf("%f",&tt);
v=3.14*r*r*l-3.14*(r-tt)*(r-tt)*l;
m=situryou();
}
d[0]=(r*r/2+l*l/12)*m;
d[1]=(r*r/2+l*l/12)*m;
d[2]=r*r*m;
j=d[p];
return j;
}

float kyuu(void){
printf("半径[mm]を入力してください\n");
scanf("%f",&r);
if(u!=4){
v=4*3.14*r*r*r/3;
m=situryou();
}
j=2*m*r*r/5;
return j;
}

float kyuukaku(void){
printf("半径[mm]を入力してください\n");
scanf("%f",&r);
if(u!=4){
printf("核の厚さ[mm]を入力してください\n");
scanf("%f",&tt);
v=4*3.14*r*r*r/3-4*3.14*(r-tt)*(r-tt)*(r-tt)/3;
m=situryou();
}
j=2*r*r*m/3;
return j;
}

float sonota(void){
printf("\n部品%dの慣性モーメント[kgmm^2]を入力してください\n\n",i);
scanf("%f",&j);
return j;
}

float situryou(void){
if(u<=3){
m=ro[u]*v;
}
return m;
}



2013/12/25

慣性モーメントを計算するプログラム作りました

慣性モーメントを計算しようとしていちいち計算するのも大変なので簡単なプログラムを作りました。

まだ使ってないので実用的かわかりませんが(回転する軸によって値が変わっちゃう)あとでどうにかします。たぶん。

というか多分ネットで検索したらフリーの計算サイトなんかが出てくるんだろうなと思ったけど努力が無駄になってしまうので検索なんてしません(絶対)。

#include<stdio.h>

float chouhoukei(void);
float nagaibou(void);
float enban(void);
float enchuu(void);
float entou(void);
float kyuu(void);
float kyuukaku(void);

float a,b,l,r,m,j; 

void main(void)
{

    int n;
    float t;

    printf("重心z軸(機力の教科書p251の図参照)まわりの慣性モーメント\n");
    printf("部品の形を番号で選んでください.\n1.長方形板,2.長い棒,3.薄い円盤,4.円柱,5.円筒形,6.球,7.球殻\n");
    scanf("%d",&n);

    if(n==1){
        t=chouhoukei();
        printf("J=%f[kgm^2]",t);
    }

    if(n==2){
        t=nagaibou();
        printf("J=%f[kgm^2]",t);
    }



    if(n==3){
        t=enban();
        printf("J=%f[kgm^2]",t);
    }

    if(n==4){
        t=enchuu();
        printf("J=%f[kgm^2]",t);
    }

    if(n==5){
        t=entou();
        printf("J=%f[kgm^2]",t);
    }

    if(n==6){
        t=kyuu();
        printf("J=%f[kgm^2]",t);
    }

    if(n==7){
        t=kyuukaku();
        printf("J=%f[kgm^2]",t);
    }
}


float chouhoukei(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("横の長さ[m]を入力してください\n");
    scanf("%f",&b);
    printf("縦の長さ[m]を入力してくだいさい\n");
    scanf("%f",&a);
    j=(a*a+b*b)*m/12;
    return j;
} 

float nagaibou(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("長さ[m]を入力してください\n");
    scanf("%f",&l);
    j=m*l*l/12;
    return j;
} 

float enban(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("半径[m]を入力してください\n");
    scanf("%f",&r);
    j=r*r*m/2;
    return j;
} 

float enchuu(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("半径[m]を入力してください\n");
    scanf("%f",&r);
    printf("長さ[m]を入力してくだいさい\n");
    scanf("%f",&l);
    j=r*r*m/2;
    return j;
} 

float entou(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("半径[m]を入力してください\n");
    scanf("%f",&r);
    j=r*r*m;
    return j;
} 

float kyuu(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("半径[m]を入力してください\n");
    scanf("%f",&r);
    j=2*m*r*r/5;
    return j;
} 

float kyuukaku(void){
    printf("質量[kg]を入力してください\n");
    scanf("%f",&m);
    printf("半径[m]を入力してください\n");
    scanf("%f",&r);
    j=2*r*r*m/3;
    return j;
} 

2013/12/24

LM2596T-ADJで電源を作りましたよっと

りゅーしです。

降圧型電源スイッチングレギュレーターIC,LM2596T-ADJを使って電源を作りました。

サーボモータを動かすときやちょうどいい電源がないときに便利です。

サーボモーターの選定

こんにちは、suzutoです。

最近はガレキ除去アームに用いるサーボモータの選定を行っていました。
まだ、アームの形状が完全には決まっていないので選定作業は完了していませんが、
とりあえず、このサーボモータは使えそうというのをいくつか見つけました。

あとは回路・システム担当の人に消費電力やPWM周期等を確認して使えるか
どうか判断してもらい、使用するサーボモーターを決めようと思います。

2013/12/22

忘年会を開きました。

Lv_ZEROです。

今年ももう10日を切りましたね。ということで昨日、サークル内で忘年会を開きました。
ここ最近はレスコンやロボットグランプリの活動に忙しかったので、いい具合に羽目を外す事ができました。
また、大学の方は、昨日から冬季休校となり、冬休みに入りました。冬休みの間はだらだらと過ごしたいと思ってしまいますが、1月の末辺りにレスコンの書類提出やロボットグランプリ大道芸競技のビデオ審査などが控えていますので、メリハリのある活動を行い、有意義な冬休みにしたいと思います。

2013/12/19

2軸ジョイスティック

contrailです。

今日は2軸ジョイスティックモジュールを使ってみました。
これを使ってサーボモータを動かしてみようと考え、まず傾けた分だけ角度を加算していくプログラムを作ろうとしてみましたが、どうもうまくいきません。原因を調べてみると、どうやら計算の途中で数値がオーバーフローしていたようで、うまくいかなかったみたいです。これを修正したところうまく動くようになったのですが、時間が遅くなってしまったので今日はサーボモータを動かすことができませんでした。

2013/12/18

感圧センサを使ってみました

 contrailです。


昨日に引き続いて、今日はArduino互換ボードRomeoで感圧センサを使って、LEDを点灯させてみました。


簡単ですが今回の回路を載せておきます。

3軸ジャイロスコープ・3軸加速度センサーモジュール

りゅーしです。

がじぇるねと、MPU-6050を使用して加速度と角速度を取得してみました

ガレキ除去アームの姿勢表示

Lv_ZEROです。

1号機のガレキ除去アームはパラレルリンクによる平行リンク機構となっており、作業範囲が直感的には分かり難かったため、Mathmaticaを使ってアームの姿勢を表示してみました。
各関節について順運動学を解き、その点を繋いで表示しています。また、Manipulateを用いて各関節角をスライダーで変更できるようにしました。
これで必要な作業範囲を満たすリンクの長さを決定するのに役立ちそうです。

2013/12/17

ガレキ除去アーム構想

suzutoです。
ガレキ除去機のアームの形状が決まりました。


記号を正しく使えているか不安です…


グランプリ試作

HIROSHIです。
日曜日に製作したグランプリ大道芸部門のロボットの機構の試作の動作確認(手動)をしました

ネジやギヤとの干渉,ラックランドピニオン機構の可動範囲,機構内の余分な隙間,実際の演技の仕様をどうするかなどいろいろと考えるべき部分が試作を作ることにより明らかになりました

Arduinoを使ってみました

contrailです。
サークルで購入したArduino互換ボード
http://akizukidenshi.com/catalog/g/gM-07045/
を使ってみました。

まずはサーボモータを動かしてみました。Arduino Unoと比べると3pinヘッダが標準で付いているので使いやすかったです。

次にフォトリフレクタを使ってLEDを点灯させてみました。

 正常に動作したようで何よりです。

TPIP開発

Dです。
TPIP開発のため、udpでの非同期通信のテストを行いました。
受信用のスレッドで文字を受信することができ、安心しました。

メカナムホイール

おはこんにちばんわ、イトゥーです。
メカナムホイールを使ってみました。
挙動が面白いのでいろいろな用途に使っていけたらと思います。
詳しい機構等はググって下さい。

2013/12/16

ガレキ除去機構想

ガレキ除去機担当のsuzutoです。

レスキューやらまいかは今年もガレキ除去機を出場させます。
そこで、新たなガレキ除去用アームについて検討したところ、アームの根元の構造は
第12回大会で使用したリンク機構を参考に作ることになりました。ということで、先輩か
ら頂いた12回大会のガレキ除去機のCADデータの中で参考になりそうなものを探した
ところ、アーム根元のギアボックスが見つかりました。





このギアボックスは回転角度を検出するために、ポテンショメータを取り付けています。
このように、アームの操作がしやすくなるようなセンサを使っていきたいと思います。

2013/12/14

これぞC++の真骨頂! タプルを実装してみた

今のVC++2013には標準ライブラリとしてtupleがあります。

std::tuple<int, double> t(100, 10.5);
std::cout << std::get<0>(t) << std::endl;
std::cout << std::get<1>(t) << std::endl;

こんな感じでstd::tuple<型リスト>という複数の型を持ったインスタンスを生成できます。
これは構造体と同じなのですが、構造体の場合は宣言しなければアクセスできませんでした。

ミーティング12/13

Lv_ZEROです。

今日というよりはもう昨日ですが、第14回レスキューロボットコンテストでのコンセプトを決定しました。
これからは決めたコンセプトに沿うように機体・システムを製作していこうと思います。

2013/12/12

定例会12/12

Lv_ZEROです。

本日の定例会では第14回レスキューロボットコンテストについてのミーティングを行いました。
フィロソフィーや規定などの再確認を行い、チームのコンセプトをどうするか話し合いました。
実際のレスキュー活動においては要救助者や操縦者に対しての配慮が非常に大切です。そのような配慮を元に、コンセプトを考え、案出しをしましたが、決定するまでは出来ませんでした。
なので、明日もう一度ミーティングを行い、コンセプトを決定し、活動方針を固めたいと思います。

話は変わりますが、明日12月13日(金)の19:00からロボット日本一決定戦!!「リアルロボットバトル」という番組が放送されます。各チームが1年近くかけて製作したリアルスケールの人型ロボット同士を戦わせるという企画です。各チームが創意工夫をしてロボット製作を行っており、非常に見応えがありそうです。興味のある方は見逃さないようにしましょう。

ロボット日本一決定戦!!「リアルロボットバトル」:http://www.ntv.co.jp/robot/outline.html

2013/12/11

今日の活動報告

Kです。今日は、いろいろテストしてみました。

 1. 手持ちのフォトカプラTLP521の周波数特性を実測。5kHzくらいから使い物にならなかった。回路を工夫すれば解消されると思われる。
2. 先輩の作ったモータードライバーの解析。Hブリッジの電源部分をFETでPWM駆動していて、その部分にふぉとかぷらが1つ、またゲートにフォトカプラ2つが使われていた。PWMの入力部分と制御しているマイコンは同電位だった。
3. 同調回路の実験。手持ちのマイクロインダクターで同調回路を作った。555で矩形波を作って同調させたところ、15kHz (1mH, 1μF)では同調された。ただし、500Hzではできなかった。
4. ステッピングモーターを2相励磁式で動かしてみました。PICから4ポートで制御します。各端子を2SC1825(小信号用だけど使ってしまった)
のベースに1kΩを介して接続。エミッタはGNDにして、コレクタとステッピングモーターのφ1, 2, _1, _2と接続。φ1, φ2, φ_1, φ_2の順番で動かす1相励磁式及びφ1φ2, φ2φ_1, φ_1φ_2, φ_2φ1と動かす2相励磁式を試した。以下ソースだがただタイマで回しているだけ。

初めPR2 = 128でやってみたら、1.35Hzだったので、PR2 * 1.35して172にしたところ1Hz=60rpmで回転するようになった。実験したところPR2 = 64では回転できなかった。
トランジスタに小信号用を使っていたり、電源はMAX1Aのレギュレータだったりとひどいので実用を考えると改良が必要。オシロスコープでトランジスタのベースを調べると、-26Vくらいの瞬間的な電圧がかかっていた。(コイルだからな……)当然、逆起電力を短絡するためのダイオードで除去できた。

#include <xc.h>

#define _XTAL_FREQ 16000000

#pragma config WDTE = OFF
#pragma config FOSC = 4
#pragma config MCLRE = 1
#pragma config LVP = OFF
#pragma config PLLEN = OFF

void main(void){
    OSCCON = 0b01101000;

    PORTA = 0x00;
    LATA = 0x00;
    TRISA = 0b00000000;
    ANSELA = 0b00000000;

    PORTB = 0x00;
    LATB = 0x00;
    TRISB = 0b00000000;
    ANSELB = 0b00000000;

    T2CON = 0b00000110;
    PR2 = 172;
    TMR2IE = 1;
   
    PEIE = 1;
    GIE = 1;
   
    while(1){
        __delay_ms(1000);
        LATA7 ^= 1;
    }
}

void interrupt INTERRUPT(){
    static char n1 = 0, n2 = 1, bn1, bn2;

    if(TMR2IF){
        LATA &= ~(0x01 << bn1); // bn1ビット目をクリア
        LATA &= ~(0x01 << bn2); // bn2ビット目をクリア
        LATA |= 0x01 << n1; // n1ビット目をセット
        LATA |= 0x01 << n2; // n2ビット目をセット
        bn1 = n1;
        bn2 = n2;
        n1++;
        n2++;
        if(n1 >= 4) {
            n1 = 0;
        }
        if(n2 >= 4) {
            n2 = 0;
        }
        TMR2IF = 0;
    }
}

5. 初代ポケモンの「じてんしゃ」の曲が完成した。PICで作った矩形波によって再生される。

C言語Tips5 おしまい!

C言語は言語仕様が大きくないので、また、このサークルではマイコン用途にしか使わないので
余り書くことがありません。
ただし、一通り入門が終わったらガシガシコードを書いていって下さい。
入門書に書いてなさそうなことは、例えば、
・グローバル変数とextern, static
・ファイルの分割と隠蔽
・typedef
・マクロ・インライン関数、インクルードガード
・オブジェクト指向
などがあります。これらも勉強しておくといいかもしれません。

さてC言語Tipsの最後にC言語理解度テストを掲載しておきますね!

0~5問正解 : 初心者でしょう……
6~9問正解 : そろそろ初心者ではないでしょう?

問世界 : もう初心者ではないでしょう!

1. 次の宣言a, b, cについて正しい説明をA~Dのなかから全て選んで下さい。
char a[] = "strings";
char* b = "strings";
char c[] = { 's', 't', 'r', 'i', 'n', 'g', 's', '\0' };

A. aは要素数7の配列である。
B. aとbはともにスタックに格納される。
C. aとcはともにスタックに格納される。
D. 変数aは書き換えられないが、変数bは書き換えられる。

2. 構造体Sが次のように宣言されているとき、s->m->mと等価な表現をA~Dのなかから1つ選んで下さい。
struct S {
    struct S* m;
};

struct S* a;

A. (*a).(*m).(*m)
B. (*a).(*m).m
C. (*(*(*a).m)).m
D. (*(*a).m).m

3. 次の演算の中で1つだけ値が異なるものをA~Dの中から1つ選んで下さい。

A. ~(-0x01)
B. 2 ^ 3
C. 1 && -1
D. -0x03 & 0x03

4. 次のように宣言された配列aにたいして、各要素のポインタを格納する配列bがある。
ある関数fは配列bのある位置(配列bの先頭の位置を0とする)xからある位置yまで探索し、最大値を返す。
関数fの引数として妥当なものを次のA~Dの中から1つ選んで下さい。

int a[8];
int* b[8];

A. int f(int* b, int x, int y);
B. int f(int* b, int* x, int* y);
C. int f(int** b, int x, int y);
D. int f(int** b, int* x, int* y);

5. 次のプログラムを実行した結果の変数xの値をA~Fの中から1つ選んで下さい。
int f(int* x) {
    return (*x)++;
}

int main(void) {
    int x = 0;
    x += f(&x) || f(&x) || f(&x);
 return 0;
}

A. 0
B. 1
C. 2
D. 3
E. 4
F. 5

6. 次の宣言の意味をA~Dの中から1つ選んで下さい。
また、この変数xを使った文として正しいものをX~Zの中から1つ選んで下さい。(ただしN, Mは十分大きいものとします。)
int *(*(*x)[N][M])(void);

A. 変数xはN×Mの2次元配列で、各要素は整数型intのポインタを返す引数なしの関数ポインタである。
B. 変数xは各要素が整数型intのN×Mの2次元配列を返す引数なしの関数ポインタである。
C. 変数xはN×Mの2次元配列のポインタで、各要素は整数型intのポインタを返す引数なしの関数ポインタである。
D. 変数xは各要素が整数型intのN×Mの2次元配列のポインタを返す引数なしの関数ポインタである。

X. *x[1][1] = (int* (*(*)[N][M])())v;
Y. (*x)[1][1] = (int* (*)())v;
Z. ((int* (*)())(*x))[1] = (int* (*)())v;

7. 次のプログラムを実行した結果のf(&x)の値をA~Fの中から1つ選んで下さい。
struct S {
    struct S* s;
};

int f(struct S* s) {
    static int x;
    x++;

    if(s->s == NULL) {
        return x;
    }
    else {
        return f(s->s);
    }
}

int main(void) {
    struct S x;
    struct S s[3];
    int i;

    x.s = &s[0];
    x.s->s = &s[1];
    x.s->s->s = &s[2];
    x.s->s->s->s = NULL;
   
    f(&x); // いくつ?
    return 0;
}

A. 0
B. 1
C. 2
D. 3
E. 4
F. 5

8. a[i][j]と等価なものを次のA~Dの中から全て選んで下さい。

A. i[a][j]
B. i[j][a]
C. i[a[j]]
D. j[i[a]]

9. (++a)++;について正しいものを次のA~Dの中から1つ選んで下さい。

A. a += 2;と等価である。
B. ++aは左辺値でないからコンパイルエラーとなる。
C. ++aは変更不能な左辺値であるからコンパイルエラーとなる。
D. ++が後置式であるからコンパイルエラーである。

10. 型int [][N]と互換性のある型を次のA~Eの中から1つ選んで下さい。

A. int *[N]
B. int (*)[N]
C. int *[]
D. int **
E. int *

正解は……

C言語Tips4 無名構造体と共用体

共用体ってあるじゃないですか。

union Data {
    unsigned int value;
    unsigned char byte;
};

これ。

C言語Tips3 ポインタを引数にとる関数にはconstを付けよう

元々C言語にはconstがありませんでしたが、C++から逆輸入されて、多くのCコンパイラに実装されています。XC8にもあります。

C言語Tips2 enum使え!

開始モード、実行中モード、終了モードとかあって、それぞれ
int mode;

mode = 0;

mode = 1;

if(mode == 1) {
    なんちゃら
}

とかしてませんか?

2013/12/07

C言語Tips1 ポインタの問題!

C言語のポインタで躓く人が多いそうです。今回は問題を出します!

問題 : 「void (*(*p)[3])(void* (*[])(void));」をmain関数の中で宣言しました。

int main(void) {
    void (*(*p)[3])(void* (*[])(void));
    return 0;
}

このpを使ってサンプルプログラムを作りなさい。回答例は続きを読むで!

2013/11/26

C#Tips7 バイト列と構造体のオフセット

まずはC#でデータをためていく方法について考えましょう。

素朴にstringに加えていきます。
class Program {
    public static void Main() {
        string str = "";

        str += "abc";
        str += "def";

        System.Console.WriteLine(str);
    }
}

C/C++から考えると恐ろしいですが、実際には+=は末尾に付け加えるメソッドになっています。

ところが文字列でないデータ列の場合、これでは読み出せません。なぜならば、
str[n]としたところでn文字目が読み出されるからです。やはり8bitごと区切られたデータが欲しい。
それではListを使いましょうか?

using System.Collections.Generic;

class Program {
    public static void Main() {
        List<byte> data = new List<byte>();

        data.Add(0x10);
        data.Add(0x20);
        data.Add(0x30);
        data.Add(0x40);

        foreach(byte value in data) {
            System.Console.WriteLine(value);
        }
    }
}

うむ……これでは配列が欲しいとき困る。
そんなときにMemoryStreamです。可変長で、好きなときにWriteで書き込みできます。(内部の実装はお察し)
データをためて行くにはWriteしていけばよいです。

using System.IO;

class Program {
    public static void Main() {
        MemoryStream ms = new MemoryStream();

        ms.Write(new byte[] { 0x10 }, 0, 1);
        ms.Write(new byte[] { 0x20 }, 0, 1);
        ms.Write(new byte[] { 0x30, 0x40 }, 0, 2);

        byte[] data = ms.ToArray();

        foreach(byte value in data) {
            System.Console.WriteLine(value);
        }
    }
}

さて本題。今、0byte目, 1byte目, 2byte目に意味のある数字が並んでおり、それぞれa, b, cと名付けましょう。
そして、0, 2byte目は8bitの非負整数、1byte目は16bitの非負整数であるとします。与えられたストリーム(byte列としましょう)からこれらに振り分けるにはどうしたらいいでしょうか?

力尽くで、if文など駆使して書けるでしょうが、d, e, f, ...と続く場合は見づらいコードになります。
ここはC/C++のような構造体を使ってシンプルに書きたいものです。が、C#にはわかりやすい書き方はありません。
正確に言うと余り推奨していないやり方なのでしょう。
以下の例は構造体にオフセットを用いて各データを並べています。
これはごく普通のやり方なのですが、System.Runtime.InteropServices.StructLayout()で属性を設定します。
LayoutKind.Explicitを指定することでFieldOffsetにより構造体の位置を指定します。

先頭が0byte目で、例えば[FieldOffset(0)]の次に来るpublic なんちゃらは、0byte目のメモリ空間を使うことになります。[FieldOffset(1)]ならば次に来るpublicなんちゃらは1byte目のメモリ空間を使うことになります。
おなじ[FieldOffset(x)]を設定したら、それらはメモリの開始位置を共有します。

byteなら1byte, shortなら2byte, intなら4byteのように領域が決まっていますが、
例えばbyte, short, intの3つのフィールドを[FieldOffset(0)]で宣言したとき、
Bxxx
SSxx
IIII
のように、共有します。即ち、byteのデータはshortの最下位バイトと共有し、intの最下位バイトとも共有するわけです。先頭なのに何で最下位か? これはリトルエンディアンと呼ばれる方式で、
0x11223344という32bitの整数は「0x44」「0x33」「0x22」「0x11」の順番でメモリに格納されます。
従って共有されるのは最下位バイトなのです。

さて、実際に宣言してみると問題が発生します。
というのも、C#のbyte配列というものはオブジェクトであって、メモリをリニアーに配置した値型ではないのです。簡単に言うとポインタであってメモリ領域ではないのです。C/C++では構造体は配列と同じくデータ領域でしかなかったため柔軟に対応できました。
この問題を解決するためにC#では固定長配列が宣言できます。ただしunsafeで。ぶっちゃけunsafeなんか使うくらいならC/C++使った方がいいと思うんですけど(外部ライブラリ作ってでもいい)、やってみました。
fixed 基本型名 変数名[データ数]
で固定長が宣言できます。

using System.Runtime.InteropServices;

class Program {
    [StructLayout(LayoutKind.Explicit)]
    unsafe struct S {
        [FieldOffset(0)]
        public fixed byte data[4];

        [FieldOffset(0)]
        public byte a;
        [FieldOffset(1)]
        public ushort b;
        [FieldOffset(3)]
        public byte c;
    }
   
    public static void Main() {
        S s = new S();
        unsafe {
            byte[] data = new byte[] { 1, 2, 3, 4 };
            int i = 0;
            foreach(byte d in data) {
                s.data[i] = data[i];
                i++;
            }
        }
        System.Console.WriteLine("{0:X}, {1:X}, {2:X}", s.a, s.b, s.c);
    }
}

もっといい方法はないかね。

2013/11/25

C#Tips6 反復子を使おう

Tips1でforeachで反復できる実装の仕方を紹介しました。
要は、foreach(★ in ●)に対して、●を返すためにSystem.Collections.IEnumerableインターフェースを実装して、GetEnumerator()によって取得できるようにしました。また、★にデータを入れるためにSystem.Collections.IEnumeratorインターフェースを実装しました。

ところでC#にも最近流行りの反復子と呼ばれる機構が備わっています。
次の例では
10
20
30
と表示されます。
反復子を実装するメソッドには戻り値としてSystem.Collections.IEnumerableインターフェース型を返します。
反復子はyield returnで、yield単体では意味を持ちません。従ってyieldという変数名は使用可能です。(しない方がいいです)
yield returnがあらわれると、値を返して、メソッド内の処理を一時中断します。
そして再びforeachによって内部でGetEnumerator()が呼ばれると前の処理から再開して次のyield returnまで実行されます。

C#Tips5 クラスの設計

クラスはC言語から見れば構造体にメソッドを付けただけのように思えます。
実はそれだけでなく継承という便利な機構があり、C#ではクラスを継承するだけでなくインターフェースというものも継承できます。今回はこれらの使い分けに迫ります。

2013/11/14

テクノフェスタ

11月10日、11日に静岡大学浜松キャンパスでテクノフェスタがあり、私たちロボットファクトリーも製作したロボットの展示や子供向けにロボットの操縦体験を行いました。
多くの子供がロボットに興味を持ってくれたようで何よりです。
来年のテクノフェスタでは自動制御されたロボットを展示できたらと思います。



C# Tips4 TCP/IP通信 クライアント編

クライアントはサーバに接続し、tcpオブジェクトを生成し、ネットワークストリームにwriteしたりまたネットワークストリームからreadしたりすることで作成できます。サンプルは以下の通り。


C# Tips3 C#からC++を実行しよう

1. C++/CLIとC#の連携
C++/CLIとは、.NET上で実行するプログラムを作るために拡張した言語です。
ためしにC++/CLIでクラスを定義し、C#から呼び出してみましょう。

(1) 新しいプロジェクトを選ぶ
(2) Visual C++のなかのCLRを選び、クラスライブラリを選択。
(3) 名前を決め、OKを押す。
(4) test.cとtest.hは消して構いませんが、他は弄らない方がいいです。
まずはtest.h内に次のように書きます。注意点はpublicとrefを付ける点です。

namespace test {
    public ref class Test {
    public:
        int x;
        Test(int x);
        int getX();
    };
}

test.cpp内には次のように書きます。

#include "stdafx.h"
#include "test.h"

using namespace test;
using namespace std;

Test::Test(int x) {
    this->x = x;
}

int Test::getX() {
    return x;
}
(5) ビルドします。
(6) 新しいプロジェクトでC#のプロジェクトを作ります。
(7) 参照に(5)でできたdllファイルを追加します。/test/Debug/test.dllにあるはずです。
(8) 次のようにしてテストします。

using System;
using test;

class Program {
    public static void Main() {
        Test t = new Test(10);

        Console.WriteLine("{0}", t.getX());
    }
}


2013/11/13

Cコンパイラの罠2

●機能レジスタのアドレスをポインタに
次にポインタにTRISAなどの機能レジスタのアドレスを代入してみました。

char* p = &PORTA;
*p |= (0x01 << 1);
のコンパイル結果は次の通りです。

movlw    high(12)
movwf    (main@p+1)
movlw    low(12)   
movwf    (main@p)

movf    (main@p),w     
movwf    fsr1l     
movf    (main@p+1),w   
movwf    fsr1h   
bsf    indf1+(1/8),(1)&7

(9サイクル)

これはTRISAの番地12のhighである0とlowである0x0cを取得してmain@pラベルの位置に書き込みます。
次にそのメモリの位置からFSR1L, FSR1Hレジスタにそれぞれ書き込み、INDF1レジスタから値を受け取ります。
(1/8)の意味するところは分かりません。bsf INDF1, 1によってビットの1番目をセットしています。
コンパイラのデータシートに書いてあるとおり、ビットのセット/クリアは次のように書けばビット命令でコンパイルしてくれます。

var |= (0x01 << N);
var &= ~(0x01 << N);

*pの指すバンクは事前に分かるわけですから、人間がアセンブリを書くとすれば次のように最適化されるでしょう。

movlb   0x00 ; バンクを0に
movlw    0x0c ; バンクの中のアドレス0x0cを
movwf    (main@p) ; main@pラベルの位置に書き込み

movlb   0x00 ; バンクを0に
bsf    (main@p), 1 ; main@pラベルのメモリにあるデータの1bit目をset

(5サイクル)

これはフリー版ですから仕方がないのですけどね。コンパイラ作りたくなってきますね。。。


XC8コンパイラの罠

1wireのライブラリでも作ろうと思ってstructを隠蔽しました。C言語ではよく使われるテクニックで、

ヘッダーファイルtest.h内で次のように宣言します。

struct _Test; // 構造体の中は宣言しない
typedef struct _Test Test; // Testでクラスのように使えるようにする

Test* Test_initialize(char n); // コンストラクタ
char Test_get_n(Test* self, void); // nを取得

あとはtest.cで実体を宣言します。

struct _Test {
 char n;
}

Test* Test_initialize(char n) {
 // メモリ確保処理が終わってTest* testが割り当てられたとする
 test->n = n;
 return test;
}

char Test_get_n(Test* self, void) {
 return self->n;
}

ところがこれはコンパイルが通りません。(メモリ確保処理は別として)どうやらXC8コンパイラでは隠蔽はできないようです。VC++ではできることを確認しました。

onewireについては後で書きます。

2013/11/11

C# Tips2 TCP/IP通信 サーバー編

こんにちは、Kです。今日はC#で非同期のTCP/IP通信を刷るプログラムを考えてみましょう。
1. IPアドレスの変換
文字列からIPAddressクラスに変換するのに、最初次のようなコードを書いていましたが、
string[] ipaddress_strings = host.Split('.');
byte[] apaddress_bytes = new byte[4];
for(int i = 0; i < ipaddress_strings.Length; i++) {
   apaddress_bytes[i] = ipaddress_strings.Parse(ipr[i]);
}
var ipaddress = new System.Net.IPAddress(apaddress_bytes);
IPAddressクラスをリファレンスで探したところ、「Parse〔IP アドレス文字列を IPAddress インスタンスに変換します。〕」というメソッドがあることが分かりました。
そういえばC#では、文字列からインスタンスを生成するのにParseという名前のメソッドを使うのが一般的でしたね。逆はToStringです。
ローカルのホスト名をDns.GetHostName();で取得できますが、これで取得するとIPv6のものまで含まれてしまい、IPv4で指定した通信には使えません。そのため、ipa.AddressFamily == AddressFamily.InterNetworkと比較することにより、次のように取得しています。
foreach(IPAddress ipa in Dns.GetHostAddresses(host)) {
   if(ipa.AddressFamily == AddressFamily.InterNetwork) {
      ipaddress = ipa;
      break;
   }
}
ただし、これでは最初のIPアドレスしか取得していないことに注意が必要です。

2013/11/09

赤外線通信でモーターを動かしてみました。

りゅーしでつ。
赤外線通信でモータを回すことができたので投稿します。

使用しているのはPIC16F1827と38kHzで受信できる赤外線受光モジュールを使用しています。
外見はこんな感じです。





2013/11/07

C# Tips1 foreachの活用

リフレッシュスペース(部室)が使えないので、駄文を書いていきます。1年生の参考になるといいな。
コンピュータなんてものは詰まるところ、条件分岐とgotoの塊なんです。決められたとおりにしか動きませんし、決められたことなら何でもできます。プログラミング言語ではif, while(for)が使えればつまるところ何でもできますが、それはとても見づらいプログラムなんですね。
例えばコントローラーを操作するプログラムを考えましょう。 コントローラーの値を取得するために、for文で取得するとします。

「定例会は定期的にやるから定例なんだよ」、おめでとうございます

どうも、Dです。
まずは無事にシリアルサーボが動いたようで何よりです。おめでとうございます。

ふとした思い付きでロボをつくろうと思って、LPC1114のボードを製作・・・。
既に持ってるLPC1768-mbed-はすでに別のロボに載せる予定なのとLPC1114をサブCPUとして使うつもりだったのでいい機会だったのですが、ちょっとでかい( ゚Д゚)が、まあ今回の用途には問題ないからこのままGO.
安くて、何かいろいろ入ってて、mbedと同環境でライタ使わず開発できるのは、こっちに本腰入れる気が無い身にとっては、やっぱり楽。
この後はアルミとでも戯れるか。

机の上は綺麗にしよう、、パソコンが使えん

おれってばかだな

2時間で片を付けると言ってから何時間たつだろうか、晴れていたはずの空も今は雨模様。外に干しっぱなしの可愛いバスタオルも今はどうなっていることやら。Kです。

未だにシリアルサーボが動かせないでいろいろ試していました。うごいたりうごかなかったりするんだよなー。3byte送って3byte受信するから、その切り替えがうまくいっていないのかなーとか値を変えつつ送ってみるも、だめ。

そこで私は送ったデータと動作の可否をExcelに書いた。

 あらやだ、ビットの数が性格に偶数でないと反応しないわ!
これはパリティのせいだと思うじゃないですか。
私はひたすらパリティをいじくった。

パリティの値を固定にしてオシロスコープを眺めてみたり、
パリティの値を反転させてみたり、
3byte目のパリティを2byte目のパリティをxorしてみたり、
毎回TX9をセットしてみたり、TX9Dをクリアしてから書き込んだり、
TXSTAを直接いじったり……・。どれも当然ながらうまくいかない。

私はUSBアダプタの信号外床が違うのかを知るためにオシロスコープを眺めた。
オシロスコープは信号の波形をしっかり記録してくれた。

図a : PICの送ったデータ

図b : USBアダプタが送ったデータ

確かに何か違う。データは当然同じものを送っているはずなのに、末尾が違う。パリティか? 私はこのオシロスコープの出力から送られているデータを読み取った。ストップビットがやけに長いな……。気づいてしまった。

ストップビットの長さが違っている。そこで次のようにして送信が全て完了するまで待機するようにした。

while(!TXIF);
 TXREG = data;
 while(!TRMT); // 送信が終わるまで待つ

TRMTは送信が全て完了したときに1になり、バッファが残っているときは0となるフラグである。TXIFはTXREGに格納できる状態の時に1, そうでなければ0となるフラグである。
TXIF, TRMT = 1のとき送信できるようにすればいいので、次のようにしてよい。

while(!(TXIF && TRMT));
 TXREG = data;

受信は、送信が完了した後で有効にする必要がある。これはTXとRXを短絡しているためで、送信データを受信と見なさないようにするためである。

// 受信許可・送信不可
    while(!TRMT); // 送信が全て終わるまで待つ
    TXEN = 0;
    CREN = 1;

    for(i = 0; i < 3; i++) {
        while(!RCIF);
        // データの取得
        g_response.data[i] = RCREG;

        RCIF = 0;
    }

    CREN = 0;

最後に連続した3byteずつの動作を送信・受信の流れに振り分けるために
for(buff = i2c->buff, end_buff = buff + i2c->buff_size; buff < end_buff; buff += 3) {
        sendSerialServoData(buff);
    }
として無事動作した。めでたしめでたし。

ああ、雨が降っていて帰れない

追記
部室に置いてあった傘を借りました。今日返します。あとラベル付け忘れたので付けておきました。

2013/10/30

とりあえずJoystick

Kです。テスト用プログラムにJoystickを使いたいので、C#からDirect Inputを使用してボタンの判定を行うクラスを作ってみました。


動作との関連づけは、各デバイスクラスにonbuttonメソッドがあり、全てのコントローラからのデータはこいつが受け取ります。設定により登録されたデバイスに対して、登録されたボタンが押されたりするとそのボタンと任意の文字列を渡す仕組みです。アナログスティックについては、常に通知する仕組みです。

joystick.setButtons(new Byte[] { 1, 3, 2, 0 }, 4);
によってキー割り当てを変更しています。
これは、左が1, 上が2, 下が3, 右が4となっているコントローラにおいて、
上から時計回りに0, 1, 2, 3と割り当てるための記述です。
また、上記4つのボタン以外は受け付けません。


using System;
using System;
using IWshRuntimeLibrary;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectInput;
using System.Windows.Forms;

using System.Collections.Generic;

class Form1 : Form {

}

class Program {
   public static void Main() {
      Device device;
      Joystick joystick;
      Form1 form;

      form = new Form1();

      device = serachDevice(form.Handle);

      if(device != null) {
         Joystick.Command jc;

         System.Console.WriteLine("デバイス見付かった");
         joystick = new Joystick(device);

         joystick.setButtons(new Byte[] { 1, 3, 2, 0 }, 4);

         jc = new Joystick.Command(0, "△");
         jc.onbutton += onButton;
         joystick.setCommand(jc);

         jc = new Joystick.Command(1, "○");
         jc.onbutton += onButton;
         joystick.setCommand(jc);

         jc = new Joystick.Command(2, "×");
         jc.onbutton += onButton;
         joystick.setCommand(jc);

         jc = new Joystick.Command(3, "□");
         jc.onbutton += onButton;
         joystick.setCommand(jc);
      }
      Application.Run(form);
   }

   private static void onButton(int no, string message) {
      System.Console.WriteLine("ID={0} / mes={1}", no, message);
   }

   private static Device serachDevice(System.IntPtr hwnd) {
      DeviceList dl;
      Device joystick = null;

      dl = Manager.GetDevices(DeviceClass.GameControl, EnumDevicesFlags.AttachedOnly);

      foreach(DeviceInstance dev in dl) {
         joystick = new Device(dev.InstanceGuid);
         joystick.SetCooperativeLevel(hwnd, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);

         // 最初に見つかったジョイスティックを操作対象とする
         break;
      }

      if(joystick != null) {
         joystick.Acquire();
      }

      return joystick;
   }
}

/*
 * ### ゲーミング用Joystickクラス ###
 * 特定のコマンドを検出したら、登録されたデリゲートに通知する。
 * setButtons(int[] )で、
 */
class Joystick {
   // pulibc
   public delegate void onButton(int no, string message);

   public Joystick(Device joystick) {
      this.joystick = joystick;

      // タイマーの作成・起動
      var mytimer = new System.Threading.Timer(new System.Threading.TimerCallback(onTimer),
         this, 0, 10);

      // ボタンルール・ボタンカウントの初期設定
      button_rule = new byte[128];
      button_number = 0;

      button_counter = new int[128];
      button_repeat_counter = new int[128];

      // コマンドリストの作成
      commands = new List<Command>();
   }

   public void setButtons(byte[] button_rule, int button_number) {
      button_rule.CopyTo(this.button_rule, 0);

      this.button_number = button_number;
   }

   public void setCommand(Command command) {
      commands.Add(command);
   }

   // private
   private Device joystick;
   private int button_number;
   private byte[] button_rule;
   private int[] button_counter, button_repeat_counter;
   private List<Command> commands;

   private static void onTimer(object sender) {
      Joystick joystick = (Joystick)sender;

      // ポーリング
      joystick.joystick.Poll();
      JoystickState js = joystick.joystick.CurrentJoystickState;

      // ボタンの検出
      byte[] buttons = js.GetButtons();

      // ここでのiは再定義した仮想番号
      // 仮想番号→実Joystick番号に変換した後データを受け取る
      for(int i = 0; i < joystick.button_number; i++){
         if(buttons[joystick.button_rule[i]] == 0x80) {
            // 通常単押しまでカウント
            if(joystick.button_counter[i] >= 5) {
               // 長押しの検出
               if(joystick.button_repeat_counter[i] > 1) {
                  foreach(Command command in joystick.commands) {
                     if(command.checkRepeatCommand(i)) {
                        command.onbutton(i, command.message);
                     }
                  }

                  joystick.button_repeat_counter[i] = 0;
               }
               else {
                  joystick.button_repeat_counter[i]++;
               }

               // 1回目だけ単押し
               if(joystick.button_counter[i] == 5) {
                  foreach(Command command in joystick.commands) {
                     if(command.checkCommand(i)) {
                        command.onbutton(i, command.message);
                     }
                  }
               }
            }

            joystick.button_counter[i]++;
         }
         else {
            joystick.button_counter[i] = 0;
            joystick.button_repeat_counter[i] = 0;
         }
      }
   }

   // 仮想番号を登録または、アナログスティックの登録
   public class Command {
      // public
      public onButton onbutton;
      public string message;

      public enum analog_direction {
         RX, RY, LX, LY, ZX, ZY, NONE
      };

      public Command(int no, string message) {
         this.no = no;
         this.is_repeat = false;
         this.ad = analog_direction.NONE;
         this.message = message;
      }

      public Command(int no, bool is_repeat, string message) {
         this.no = no;
         this.is_repeat = is_repeat;
         this.ad = analog_direction.NONE;
         this.message = message;
      }

      public Command(analog_direction ad, string message) {
         this.no = 0;
         this.is_repeat = false;
         this.ad = ad;
         this.message = message;
      }

      public bool checkCommand(int no) {
         // アナログスティックの場合は無条件
         if(ad != analog_direction.NONE) {
            return true;
         }
         
         // コマンドの照会
         return this.no == no;
      }

      public bool checkRepeatCommand(int no) {
         // コマンドの照会
         return this.no == no && is_repeat;
      }

      // private
      private int no;
      private bool is_repeat;
      private analog_direction ad;
   }
}

2013/10/25

to ka  です。
CADで試作品の角材を作ってみました。

2013/10/24

浜松基地に行ってきました

Contrailです。

サークル活動とは関係ありませんが、日曜日に大学の北にある空自の浜松基地で航空祭があったので行ってきました。

あいにくの天候で、予定されていたF-15の機動飛行やブルーインパルスの演技が中止になってしまいました。ただ、気になっていたF-15のエンジンノズルの機構を見ることができたので、それは良かったです。

来月の岐阜基地の航空祭も天候がよければ見に行きたいと考えています。

また、来月9,10日にあるテクノフェスタにサークルで展示をすることになっていて、それに向けて現在活動中です。

2013/10/23

C#からRubyを実行★IronRuby★

Kです。本日2回目の投稿です。というのも、テーマごとに1つの記事にすべきなので1日に何回投稿しても構いません。メールがうざったい人は管理できる人に言ってリストから削除して貰って下さい。尚、私は削除しました。笑

さて、本題のIronRubyに関して、まずは動機を説明しましょう。先日の操縦テストプログラムの要望によって、スクリプトファイルで処理した結果をデバイスに送信する必要が出てきました。従って、C#から数値の配列を渡し、Rubyが処理し、その結果を数値の配列で受け取る必要が出てきました。

次に、必要な環境について説明しましょう。IronRubyはC#上でRubyのソースコードを解釈し、実行するプログラムです。これを組み込めば、上記の目的は達成できるでしょう。さて、これを使うには次の4つのdllファイルを参照に追加する必要があります。
  • IronRuby.dll
  • IronRuby.Libraries.dll
  • Microsoft.Dynamic.dll
  • Microsoft.Scripting.dll
これらは、公式サイトからダウンロードできます。尚、Microsoft.Dynamic.dllについてはdynamicを使用しない場合は要らないようです。(適当)

とりあえずネット上から探してコードを実行してみました。

using System;
class Program {
 public static void Main() {
  var engine = IronRuby.Ruby.CreateEngine();
  var scope = engine.CreateScope();

  engine.ExecuteFile("./test.rb", scope);
  var ss = engine.CreateScriptSourceFromString("foo()");
  ss.Execute(scope);

  int res = engine.Execute<int>("fooo", scope);
  Console.WriteLine(res);
 }
}
test.rbの中身は以下の通りです。ちなみにRubyの流派ではreturnを書きません。
def foo
 $x = 10
end

def fooo
 return $x
end
これを実行すると、10と表示されます。

中身を追っていきます。 IronRuby.Ruby.CreateEngine();でRubyのインタプリタエンジンを作成します。

次に、engine.CreateScope();で変数などを記録するフィールド(適当)を作成します。このscopeが異なると別々の領域で実行されることになってしまいますので注意。

次に、engine.ExecuteFile("./test.rb", scope);でファイル都市コープを指定して実行します。実行された結果、値が返ってきますが、今回はメソッドの定義しかしていないのでその必要はありません。

 次に、var ss = engine.CreateScriptSourceFromString("foo()");でfooというメソッドを呼び出すプログラムを作成し、そのプログラムを変数ssに代入しています。これは次の行のとおりss.Execute(scope);と実行できます。ループして実行する場合は一度プログラムを解釈した後にまとめてExecuteしたほうが速かったです。

 最後にint res = engine.Execute<int>("fooo()", scope);でfoooメソッドを実行しました。結果をintで受け取り、ごにょごにょしています。文字列の場合はstringを、配列の場合はRubyArrayを指定します。

 その後いろいろ調べていたら、 次のコードでも実行できることが分かりました。但し参照にMicrosoft.CSharpを追加する必要があります。
using System;

class Program {
 public static void Main() {
  var rt = IronRuby.Ruby.CreateRuntime();
  dynamic t1 = rt.UseFile("./test.rb");
  dynamic t2 = rt.UseFile("./test2.rb");

  t1.foo();
  t2.bar();

  Console.WriteLine(t1.fooo());
 }
}
 Rubyは次の2ファイルです。1つめがtest.rbです。
def foo
 $x = 10
end

def fooo
 return $x
end
2つめがtest2.rbです。
def bar
 $x = 20
end

さてこの実行結果は20を表示します。

まず、var rt = IronRuby.Ruby.CreateRuntime();でRubyのランタイムを作成します。(適当)次にdynamic t1 = rt.UseFile("./test.rb");でソースコードを読み込み、解析結果をt1に代入します。このとき、dynamicを指定します。これは動的に動作が変わるためです。そしてt1.foo();で実行します。

この方法ではRuby内のメソッドをC#のメソッドで呼び出せます。更に、1つめのサンプルに比べてかなり高速です。これを使うしかありませんね!!

ファイルを分割しても、同じスコープを共有されるためグローバル変数が上書きされました。

デザインを変えてみた

Kです。Bloggerのデザインを変えてみました。

初め、クラシックテンプレートで編集していましたが、細かいことが出来ないことに気づいたので(「タグ」があまりじゃないか。)適当にテンプレートを選択しました。こいつのせいで2時間くらい無駄にしました。ああ最悪!

これでも不満はばっちりなんですけどね。まず、ブログ全体の幅がpx指定じゃないといけないこと。今時どの端末で見ているかもわからないのにどうして%指定じゃないのでしょうか。px固定とか10年前かっての!他にも投稿者と時刻の表示がおかしい点や上げればきりがないのですが;

更に、bloggerに限らず段落分けの機能がないことが非常に不満です。未だにbr要素で改行を表現しているんですよ? もう2013年ですよ。 10年m(ry

いいですか、今時のブログは意味づけが出来るようなエディタであるべきです。基本は段落とリスト、表で構成されており、必要ならば強調、リンク、削除など付け加えればいいのです。

もういろいろ面倒になってきたのでこれでいいや。あ、デザイン変更するとシンタックスハイライトのコードまで削除されちゃうから注意ね!

2013/10/22

操縦テストプログラムの仕様

こんばんはKです。いろいろなロボットを制御するにあたって、ひとまずPC上からコントローラーなど使って動かせるように汎用的なプログラムを作る予定です。できれば今週中に仕上がるといいな

仕様は以下の通り :

自由操作プログラム仕様

1. 本ソフトの目的
本ソフトはPC上からマイコンに対してデータを送るプログラムである。
即ち、ある入力x_nに対して出力y_n = f(x_n)を出力するプログラムである。

例えば、コントローラAの○ボタンを押すことにより、内部の制御データa1の値をα1にセットする、
その結果、y_1 = βα1γを出力するといった具合である。

2. 入力インタフェース
・マウス
・キーボード
・ジョイスティック
を想定する。これらの入力データは内部の別の記号σ_nに割り当てられる。

デジタル入力に対しては、
・長押し
・単押し
が存在する。内部ではσ_nがそれらを判定できる。

アナログ入力に対しては、
・数値読み取り
が存在する。内部ではσ_nがデータを担う。

3. 出力データ
出力データ列βα_nγは、入力σ_nに対して適切なデータ列α_nを生成し、その他デバイスの要求するプロトコル
に変換したものである。
βα_nγ = d_n(αn)となる写像d_nをデバイス設定と称する。

今回は簡単のため、デバイスは最大で1つまで同時に使用可能ということにしよう。

4. プログラム全体の流れ
(1) PCに接続されているジョイスティックを全て取得
(2) ジョイスティックを識別する設定ファイルを読み込み、関連づける
(3) デバイスの設定を読み込み、ジョイスティックの各ボタンに関連づける
(4) ジョイスティックの入力に対して、デバイスが関連づけられていればデバイスに挙動を委ねる
(5) 操作が終了したらデバイスの設定に基づき、データを生成してデバイスに送る。

4-1. システム上の課題
(1) ジョイスティックの認識と取り扱いが必要
(2) 設定ファイルの形式やその取り扱いが必要
(3) (2)と同様
(4) 特になし
(5) COMポートを用いた通信が必要

4-2. より汎用的にするためには
デバイスとの通信は究極の所、内部の持つ生の設定値α_nを変換して出力データ列βα_nγにすることである。
この変換するプログラムをプログラム中に書いては任意のデータ処理が出来ない。
従って何らかのスクリプトにして外部に追いやることが必要である。
即ち本ソフトのなすべきことは、GUI上でコントローラーやキーボードなどを用いて
データを送りつけることである。

これを実現するためにはWSHを利用したプログラムを書く必要がある。
今回はWSHを利用する。

5. GUI
メイン画面はデータの出力状況を表示する。データとは、例えばPWMを出力するボードなら各ポートの値である。
究極的には数値データしかないのだが、要望があればよりわかりやすい表現を用いる。

設定画面は、ジョイスティックとそれに対する関連づけ画面と、デバイスの設定画面に大別される。

・ジョイスティックとそれに対する関連づけ画面
現在接続されているジョイスティックが一覧で表示され、使用する場合その設定を行う。
まず、ジョイスティックに名前を付ける。この名前を用いて他の設定をする。
次にボタンに名前を付ける。正確には、ボタンに名前を付けて使用リストに追加する。
directinputを使って、使用リストの中を常に監視し、ボタンが押されたり長押しされたり値が変更されたりしたときに、関連づけられたデバイスにメッセージを投げつける。

・デバイスの設定画面
デバイスの入力に対して出力をするスクリプトを指定する。
そのほか、デバイスの通信設定だとか、デバイス名だとかも取得できる。

本ソフトを使用するデバイスとしては、
データ8bitストップ1bitパリティなしの他に、
・デバイスIDの取得
・起動回数の取得
が出来ることを推奨している。


6. クラス
Input 入力を担う
    GameController コントローラー。使用ボタンを管理する
        Button ボタン。ここにデバイスが関連づけられる
    Keyboard キーボード。ここにもデバイスが関連づけられる。
    Mouse マウス。ここにもデバイスが関連づけられる。

Device
    BasicDevice スクリプトを利用しないシンプルなデバイス
                # 出力データは8bitを1つとして、任意バイト数送りつける。フレーム単位*で送る。
    AdvancedDevice スクリプトを利用するデバイス

Script スクリプト処理を管理する。
Setting 設定データの管理



2013/10/21

便利なソフト★かちゃかちゃうぃんどうず★

お久しぶりです。Kです。

今回はみなさんにマウスを極力使わないで作業効率を高める便利なソフトウェアを紹介したいと思います。キーボードとマウスを行ったり来たりするのは時間がかかります。多少の時間だと思っても、その回数が多くなればなるほどいらいら……。慣れれば作業効率が上がります。

1. bluewind
このソフトは、他のソフトを呼び出すためのランチャーです。よく使うソフトを〈コマンド〉として登録して、それを入力することによって実行できます。
もちろん、コマンドは全て入力する必要はなく、数文字で特定できれば確定されます。
そのほか、c:\……と打てば、そのフォルダを表示できたり、再起動のコマンドやシャットダウンのコマンドなど登録できます。
例えばCtrl + Spaceをこのbluewindに割り当てれば、Ctrl + Spaceを押せば入力欄が出てきて、起動したいソフトの名前の先頭数文字を打てば起動してくれます。
応用すれば、「google あいうえお」と入力すればgoogleで検索、「cal 100+100」と打てば200と計算可能、なんてことも出来ます。ぱっとすぐに何かしたいときに大変便利です。

2. あふ
このソフトは、2画面ファイラーです。2つのディレクトリが画面上に表示されており、ファイル名や日付、サイズなどが一覧表示になっています。キーボード上から例えば、space keyで選択し、Dキーで削除、Mキーで反対側のディレクトリへ移動、Cキーで反対側にコピーなどとファイル操作が可能です。各キーの設定は細かく出来、例えば他のソフトウェアに現在のカーソル上のファイルを渡して処理することなどが可能です。
 私は、例えばmfindというソフトを使ってファイル内の文字列の置換といった処理をしたりします。ディレクト以下にあるファイルの文字列を一括で置換あるいは検索が出来便利です。
Migemoが使えるのもポイントです。ディレクトリ上に大量にファイルがある場合、目的のファイルを探すのには苦労しますが、先頭の名前が分かるのなら、ローマ字入力で平仮名片仮名はもちろん、漢字までも検索してすぐにカーソルが移動します。
ディレクトリ移動は親のディレクトリに移動したり、このディレクトリに移動したり、ブックマークを設定してその中から選ぶことが出来たりします。ただ、やはり特定のディレクトリにジャンプしたい場合があります。bluewindを使っても実現可能ですが、少し検索機能が貧弱なのと、ディレクトのみに特化したい場合があります。また、左ウィンドウ・右ウィンドウのどちらかにジャンプしたい場合があります。こんなときは3. fennrirを使うといいでしょう。あふから例えばJキーでfenrirを起動するようにします。そして、例えばEnterキーで現在のfenrirに入力されているディレクトリをあふの左メニューに、Ctrl + Enterで右メニューにするように設定します。これでパソコン上のフォルダをあふですぐに開けるようになります。
ファイルが色別に出来ることは当然ですが、マスク機能が便利です。特定の拡張子のみ表示することが出来ます。私は;キーで現在のカーソル上のファイルの拡張子のみを表示、:で解除、/でファイルのマスクを入力(例えば.pngのみ表示したければ*.pngとする)としています。これらは自分で自由にカスタマイズできます。(する必要があります。)
そのほか、画像ビューアとしても使えたり、ファイルを実行するとき、コマンドラインから実行が出来、CUIなアプリケーションも引数を簡単に指定して実行できたり、圧縮ファイルを展開できたり、展開せずに中身だけ閲覧できたり、属性や更新日時の変更が出来たり、おそらく創造することは何でも出来るでしょう。

足りない機能があれば、他のスクリプト言語やソフトウェアを組み合わせることで無限の可能性を秘めています。



3. fenrir
2.あふで述べたとおり、あふとともに用いるととても便利なランチャー兼ファイラーのソフトです。(もちろんfenrir自体はあふとは全く関係がありません。)
このソフトはパソコン上のディレクトリやファイルを検索してインデックスに記録します。そのため、検索が非常に高速で目的のファイルやディレクトリを一発で 探してくれます。migemoが使えるのでローマ字でも(変換せずに)検索できます。

4. Yet Another Mado tsukai no Yuutsu
このソフトはキー入力のカスタマイズを行うものです。例えば、←キーが押しづらい人は、Alt + Hで直接置換してしまうことができます。つまり、←キーを押す代わりにAlt + Hとおせば、Windows上では←キーと認識されます。このような単純な置換だけでなく、内部でトグル出来たり、2ストローク(例えばCtrl押しながらHを押した後にAを押すといった一連の流れを1つのコマンドとする)が出来たりと非常にカスタマイズ性が高い。
はっきり言ってこのソフトに慣れると他の環境で何も出来なくなりますので注意。


5. ckw-mod
このソフトはコマンドプロンプトを便利にした。WIndowsでもコマンドを入力することはしばしばあります。そんなとき、日本語を入力したくてもAlt + 半角全角を押さなければならないし、コピーは選択してコピーを押さないといけません。
このckw-modを使えば、フォントが自由に選べたり、色(のセット)を自由に変更できたり、操作が簡単になったり、シェル(6. NYAOSをお勧めします)を選べたり自由にカスタマイズできます。
例えばあふで、現在のディレクトリをカレントディレクトリとしてckw-modを開くコマンドをVとしておくととても便利です。

6. NYAOS
cmd.exeのコマンドを拡張したcmd.exeの本体の代替です。ckw-cmdはその外枠に過ぎず、nyaosを使って処理をします。スタックによるファイルの操作や強力な履歴機能、lsコマンド、エイリアスや関数などで強力なコマンド実行が出来る。ただ、私はlsコマンドしか使っていないw (あふがあるので余り出番がない)
たぶん、サークルではipconfigとpingくらいしかお世話になりませんが;

7. サクラエディタ
カスタマイズが隅々まで出来るテキストエディタです。各ファイルの設定からツールバーは当然、メニューバーや本当に細かい全てのキーの割り当て、右クリックメニューとなんでも設定可能。マクロをJScriptで書くことが出来るので好きな処理を自由に追加できます。文字の検索・置換に正規表現(鬼車)が使えるため、検索・置換は非常に強力。文字の強調にも正規表現が使えるため、例えば「\(円マーク)から始まってアルファベットが続く文字列を青で強調表示」といった設定が可能。即ち、IDEのような色分けを設定できます。
入力補助機能も充実。補完入力は、設定ファイル(例えば拡張子が.htmlの場合……など)によって保管ファイルから保管する文字列を選択可能。入力途中で 完成形が出てくるので全部入力しなくても良くなります。
そのほか、バックアップ、Grepによるファイルの検索、Diffによるファイルの差分、文字を打つたびに検索するインクリメンタルサーチ(Migemo併用でローマ字で日本語を検索できます)、タグファイルを作成してタグジャンプ、矩形選択などが出来ます。
マクロと併用することにより8. TeXの開発環境とすることができます。例えば編集中そのソースをコンパイルするマクロを書くことによって、Ctrl + Shift + Cでコンパイルを実行するように割り当てればすぐに実行できます。当然、結果をpdfで閲覧なども可能です(別のpdfビューアが必要です)。



8. TeX
TeXは文章や画像を張るための領域、数式などの図をきれいに整形してdviやpdfなどのファイルに出力するソフトです。きれいな数式が書けたり、自動で図形を並べてくれたりとwordとは違った点で便利なソフトです。レポートなど決まった書式に従って文章をマークアップしていく場合に大変重宝します。
マクロを使うことにより拡張可能で、既にそれらを使った便利なスタイルファイルが用意されています。これらを利用すればソースファイルを読み込んで取り入れたり、センター試験風の描画が出来たり、関数グラフが書けたり思うままです。
機能についてはここでは列挙しきれないほど豊富で、書籍も参考Webサイトも豊富なので必要に応じて参照して下さい。





 9. charu3
クリップボード管理ソフト。貼り付けた彝文字を登録しておいて、例えばShift2回押して呼び出すなどとすることにより、定型文の貼り付けが可能。
このソフトがいいのは、マクロとして選択文字列を取得してごにょごにょできたり、シェルの実行として、他のアプリケーションに文字を渡したりできる点です。これにより強力なその場編集機能がどのソフトでも使えることになります。
もちろんクリップボードとしての機能もあり、スタック・キュー形式のクリップボード管理が可能です。例えば、A, Bとコピーしてから2回貼り付けするとB, Aと貼り付けられたり、A, Bと貼り付けられたり出来るわけです。


10. ruby
これはスクリプト言語Rubyです。強力な文字列処理ができ、高級なbatch fileを作ったり、ちょっとした出力が欲しい場合に活用できます。
例えば、64サンプルのsinのテーブルが欲しい場合、
64.times do |i|
print (Math.sin(i/64.0 * Math::PI) * 128 + 128).to_i
print ", "
end
などとして出力を得ます。

これらのソフトにより作業がより楽しく、効率的になったらうれしいです☆

2013/10/20

C#で画像処理-WEBカメラの使用-

Lv_ZEROです。

前回予告したように今回はUSB接続のWEBカメラから画像を読み込む方法を紹介していきたいと思います。
windows環境でUSB接続のWEBカメラを使用するには2通りの方法があります。1つは"DirectShow"を使う方法で、もう一つは"VFW(video for windows)"を使う方法です。"AForge.NET"では前者の"DirectShow"を使ってWEBカメラから画像を読み込む方法が実装されています。

それではプログラムの説明をしていきます。

ライブラリを使用するために「プロジェクト(P)」→「参照の追加(R)」から
AForge.Video.dll
AForge.Video.DirectShow.dll
をプロジェクトに追加しておいてください。さらに、
using AForge.Video;
using AForge.Video.DirectShow;
もプログラムに追加しておいてください。

WEBカメラを使用する時のプログラムの流れは以下のようになります。
①接続されている全てのビデオデバイスの取得
//ビデオ入力デバイスのみ取得
FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
②使用するビデオデバイスのオブジェクト生成
//VideoDevices中のデバイス1つを選択し、そのオブジェクトを生成
int Index;
VideoCaptureDevice videoSource = new VideoCaptureDevice(videoDevices[Index].MonikerString);
//最初に見つかったビデオデバイスを使用する場合、以下のようにIndex = 0とすればよい
VideoCaptureDevice videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
③フレームサイズなど、ビデオデバイスの設定
//ビデオデバイスの提供する機能一覧を配列に格納
VideoCapabilities[] videoCapabilities = videoSource.VideoCapabilities;
//提供されているフレームサイズをコンボボックスのアイテムに追加
foreach ( VideoCapabilities capabilty in videoCapabilities )
{
    comboBox.Items.Add( string.Format( "{0} x {1}", capabilty.FrameSize.Width, capabilty.FrameSize.Height ) );
}
comboBox.SelectedIndex = 0;
//コンボボックスで選択されているのフレームサイズに設定
videoSource.VideoResolution = videoCapabilities[comboBox.SelectedIndex];
//このプログラムのままだと"SelectedIndex"が常に0となってしまうので、別のメソッドで設定するするように
④新しいフレームが来たら発生するイベントの登録
//新しいフレームが来たら"video_NewFrame"という名のメソッドが呼ばれる
//"=="でなく"+="になっている点に注意
videoSource.NewFrame += new NewFrameEventHandler(video_NewFrame);
⑤ビデオデバイスの起動
//ビデオデバイスの起動
videoSource.Start();
⑥新しいフレームが来たら処理(繰り返し)
//新しいフレームが来たら実行されるメソッド
private void video_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
    //新しいフレーム"eventArgs.Frame"のコピーを"Bitmap"に格納して扱う
    Bitmap img = (Bitmap)eventArgs.Frame.Clone();
    //ここで画像処理などを行う
    pictureBox.Image = img;//画像を"pictureBox.Image"に表示
}
⑦ビデオデバイスの停止
//ビデオデバイスの停止
videoSource.SignalToStop();
//完全に停止するまで待つ
videoSource.WaitForStop();
//これ以上使用しないならオブジェクトを空にする
videoSource = null;

細かいメソッドの役割は以上です。これを組み合わせてプログラムを作ります。
まだ紹介していないメソッドなどもありますが、基本的にこれだけ分かっていれば、自分で調べて実装することもできるでしょう。

最後に、実際にwindowsフォームで作ったプログラムを公開しておきます。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//名前空間の省略
using AForge.Video;
using AForge.Video.DirectShow;

namespace CSharpで画像処理
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //接続されている全てのビデオデバイス情報を格納する変数
        private FilterInfoCollection videoDevices;
        //使用するビデオデバイス
        private VideoCaptureDevice videoDevice;
        //ビデオデバイスの機能を格納する配列
        private VideoCapabilities[] videoCapabilities;

        //ウィンドウが生成された時
        private void Form1_Load(object sender, EventArgs e)
        {
            comboBox2.Enabled = false;
            button1.Enabled = false;
            button2.Enabled = false;
        }

        //"デバイス取得"ボタンが押された時
        private void button3_Click(object sender, EventArgs e)
        {
            videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            if (videoDevices.Count != 0)
            {
                comboBox1.Items.Clear();
                foreach (FilterInfo device in videoDevices)
                {
                    comboBox1.Items.Add(device.Name);
                }
                comboBox1.SelectedIndex = 0;
                comboBox2.Enabled = true;
                button1.Enabled = true;
            }
            else
            {
                comboBox1.Items.Clear();
                comboBox1.Items.Add("デバイスが見つかりません");
                comboBox1.SelectedIndex = 0;
            }
            
        }

        //デバイスが変更された時
        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            videoDevice = new VideoCaptureDevice(videoDevices[comboBox1.SelectedIndex].MonikerString);
            videoCapabilities = videoDevice.VideoCapabilities;
            comboBox2.Items.Clear();
            foreach (VideoCapabilities capabilty in videoCapabilities)
            {
                comboBox2.Items.Add(string.Format("{0} x {1}", capabilty.FrameSize.Width, capabilty.FrameSize.Height));
            }
            comboBox2.SelectedIndex = 0;
            videoCapabilities = videoDevice.VideoCapabilities;
            comboBox2.Items.Clear();
            foreach (VideoCapabilities capabilty in videoCapabilities)
            {
                comboBox2.Items.Add(string.Format("{0} x {1}", capabilty.FrameSize.Width, capabilty.FrameSize.Height));
            }
            comboBox2.SelectedIndex = 0;
        }

        //フレームサイズが変更された時
        private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
        {
            videoDevice.VideoResolution = videoCapabilities[comboBox2.SelectedIndex];
        }

        //"起動"ボタンが押された時
        private void button1_Click(object sender, EventArgs e)
        {
            videoDevice.NewFrame += new NewFrameEventHandler(videoDevice_NewFrame);
            videoDevice.Start();
            button1.Enabled = false;
            button2.Enabled = true;
            button3.Enabled = false;
            comboBox1.Enabled = false;
            comboBox2.Enabled = false;
        }

        //新しいフレームが来た時
        void videoDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            Bitmap img = (Bitmap)eventArgs.Frame.Clone();
            //ここで画像処理
            pictureBox1.Image = img;
        }

        //"停止"ボタンが押された時
        private void button2_Click(object sender, EventArgs e)
        {
            if (videoDevice.IsRunning)
            {
                videoDevice.SignalToStop();
                videoDevice.WaitForStop();
            }
            pictureBox1.Image = null;
            button1.Enabled = true;
            button2.Enabled = false;
            button3.Enabled = true;
            comboBox1.Enabled = true;
            comboBox2.Enabled = true;
        }

        //ウィンドウを閉じる時
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (videoDevice.IsRunning)
            {
                videoDevice.SignalToStop();
                videoDevice.WaitForStop();
                videoDevice = null;
            }
        }

        

        
    }
}

本来ならtry-catch構文でエラー回避するべきですが、コードが見ずらくなると思ったので実装していません。

これでUSB接続のWEBカメラから画像を読み込むことができましたので動画像処理も可能となりました。
次からは"AForge.NET"での画像処理を試していきたいと思います。

2013/10/18

C#で画像処理-AForge.NETの導入-

Lv_ZEROです。

C#で画像処理と言えば"OpenCV Sharp"が有名ですが、個人的には導入が面倒だったり、うまくいかなかったりと導入が大変でした。そこで、導入が簡単で、尚且つ、高性能な画像処理系のオープンソースはないかと探していたところ"AForge.NET Framework"というのを見つけました。

"AForge.NET"はコンピュータビジョンと人工知能の分野で開発や研究のために開発されたC#のためのフレームワークだそうです。主な機能は以下の内容のようです。

  • 画像処理(Kinectも対応しているようだ)
  • ニューラルネットワーク
  • 遺伝的アルゴリズム
  • 機械学習
  • ファジィ論理
  • Lego Mindstormなどのためのライブラリ

機能だけ見るとロボット工学において必要なものばかりです。しかしながら、少ししか使用していないので何が出来て、どれくらいの性能なのか、まだわかりません。情報も少なく、英語のドキュメントが唯一の手がかりな感じですが、少しずつ使い方を覚えていきたいと思います。

"AForge.NET"のホームページは以下のURLから行けます。(もちろん英語です)
http://www.aforgenet.com/

それでは、"AForge.NET"の導入方法を紹介します。

1.公式HPから"AForge.NET Framework"をダウンロードします。

2.ダウンロードしたファイルを解凍し、「Release」フォルダの中にあるdllから必要なものだけプロジェクトフォルダの中にコピーします(もちろんコピーしなくてもできます)。

3."Visual Studio"の「プロジェクト」→「参照の追加」→「参照」をクリックして、必要なdllを選択し、プロジェクトに追加できたら導入完了です。

超簡単でしたね。これでライブラリの使い方さえわかれば直ぐに開発が始められます。

"AForge.NET"による画像処理のイメージフォーマットは"Bitmap"です。しかしながら、一丸に"Bitmap"といえど、8bpp,16bpp,24bpp,32bppなどといったようにフォーマットが異なります。
"AForge.NET"ではグレースケールなら8bpp、RGBなら24bppとフォーマットを定めているようです。そこで適切なフォーマットに変換してくれるメソッドがあります。
// 画像の読み込み
System.Drawing.Bitmap image = (Bitmap) Bitmap.FromFile( fileName );
// 適切なフォーマットに変換
AForge.Imaging.Image.FormatImage( ref image );
あとは適切なフォーマットになったBitmapを使って色々処理をしていく形になります。

今日はここら辺で、次回はUSBカメラから画像を取り込む方法を紹介したいと思います。

2013/09/20

部員の皆様へ_市販品の3Dデータ共有化

部員向けです。Deanより
設計の効率化、時間短縮のため、機械部品などの3Dデータを共有しましょう。
私のAutodesk360に市販品の3Dデータを置いてあるフォルダがありますので、まだ共有されていない方は全体メールで私に連絡してください。

・Autodesk360について
Autodesk Inventor2014をインストールしている人は、AutodeskからAutodesk360というオンラインストレージが提供されているかと思います。これを活用すれば自分の担当しているプロジェクトを他のメンバーと共有することも可能です。他のメンバーと連携を取ることで、ミスの早期発見や今回のレスキューロボットコンテストでも問題になった回路置き場についても実際に部品を製作する前に協議することができます。ぜひ活用してください。
使い方がよく分からないという方は、私に言ってくださればできる限り対応します。

9/20更新分

・アームサポーター40XX.ipt
・アームサポーター25XX.ipt
・アルミフリーホーン2500A.ipt
・アルミクランプホーン.ipt
・KRS-25XX.ipt
・KRS-40XX.ipt
・KRS-60XX.ipt
・Futaba_ServoHorn_TypeD.ipt
※iPropertyは調整していないので、使用する際は自分のプロジェクトにコピーしてから設定をしてください。

2013/09/16

シリアルサーボ

Deanです、こんばんは。
個人的な趣味で、近藤科学さんのシリアルサーボ、KRS-2542HVをいじってました。
KONDOのホームページやインターネットの情報を参考に、ARMマイコンボードmbedからIDの読み込みとサーボのポジションを移動することができました(データの受信がビミョーなところですが...)。電流や温度の取得ができるようなので、今後はそちらも試しつつポジション設定を変数化してみようと思います。

当初の予定ではRCB-4HVと接続し、mbedをコントローラーとの中継役にしようかと考えていましたが、電圧降下の問題がなければ(解決すれば)mbed単体で制御できるかも(/・ω・)/

ICS3.5では最大1.25Mbpsで通信可能とのことですが、mbedのシリアル通信の最大通信速度ってどのくらいなんでしょうかね?

2013/07/08

レスキューロボットコンテスト予選終了

タイトルの通り、先日レスキューロボットコンテストの東京予選が終了しました。
ハイ、落ちました。散々な結果でした。今週の木曜にでも反省会をやろうと思います。今回、ほとんどのロボットを期限までに完成させることができなかったので、7,8月の間にそれらの完成を目指します。
本日は荷物の簡単な掃除を行いました。明日も続けて行います。

本日のホワイトボードメモ
・現在無い径のドリルを探すor買う.//折れたドリルも新しいのを買ってなかったので
・バンドソーの刃を買う//一個あたりの価格が高いですが
・良い感じのスライドレールを買う(ペアで)。//あればそれを使って直動機構を作ろうとするんじゃ  ね?との声が
・反省会をする時、顧問の先生をお呼びする。
・競技会本選を見に行くかどうか。
ではまた

2013/06/19

PWM・シリアル変換基板の作成

どうも3年のKです。

先輩のプログラムがどうしてもコンパイルできないので、設定の変更で使うためにPWM・シリアル変換基板を作成しました。

立ち上がり時にタイマー1を記録し、立ち下がり時に差分を計算し、その結果をPWM値と設定する。
一定間隔で値をシリアル信号で出力するといったものです。

パリティの計算は以前のサイトを参考に

parity = (data & 0x55) + ((data >> 1) & 0x55);
parity = (parity & 0x33) + ((parity >> 2) & 0x33);
parity = (parity & 0x0f) + ((parity >> 4) & 0x0f);
parity &= 0x01;

としてみました。 おそらくちょっとは速くなっていると思います。

XC8では256byteより大きい配列が扱えないので次のように分割しました。

switch(diff.high){
    case 0:
        g_pwm[i].value = COUNT_TO_PWM0[diff.low];
        break;
    case 1:
        g_pwm[i].value = COUNT_TO_PWM1[diff.low];
        break;
    case 2:
        g_pwm[i].value = COUNT_TO_PWM2[diff.low];
        break;
    case 3:
        g_pwm[i].value = COUNT_TO_PWM3[diff.low];
        break;
    case 4:
        g_pwm[i].value = COUNT_TO_PWM4[diff.low];
        break;
}

しかし問題はつきもの。どうもたまに変な値を取得したのか、変な挙動がします。
元のPWM信号に問題があるのか変換基板に問題があるのか。

元の出力信号の精度を上げるために
・PICのA/D変換
・PICのデジタル出力
をそれぞれ確認してみたところ、問題はありませんでした。(多少の改善はしましたが)

これは変換基板側でどうにかするしかありません。

 とりあえず変化+10以上では直接データを書き換えないで、もう一回の測定を待つようにしましたが、どうも細かい調整が出来ません。困ったなあ

2013/06/16

カメラがいい感じ

どうもKです。

秋月からカメラが届いたので早速映してみました。
中々いい画質で、操縦も楽になりそうです。
カメラの切り替え用の基板が欲しいところですね。 # 作らないと……

そういえば今日はI2Cに苦戦していましたのでそのメモでも書いておきますね。 # 低脳

* I2C動作しないメモ

SSP1IE = 0;
SSP1IF = 0;
SSP1CON2bits.SEN = 1; // スタートコンディションの発行
while(!SSP1IF); // 発行終了待ち←止まる

SSP1IFが1にならない。割り込みが起こってしまっているのでは? と思い、SSP1IFを割り込み内でクリアするようにしてもだめ。

“In Master mode, the SDAx
and SCKx pins must be configured as INPUT”
まじかよw 設定していなかったので再度設定し直すことに。

やっと次のステップに進めた。

続いてアドレスのマッチングは行われたようだが、送られてくるデータが違う。
IDを変更してIDが一致しないと動作しないことを確認した。

どうやら送られてきていたデータはIDだったようで、
IDが一致していればそのIDがそのままSSPBUFに格納される仕様だったようだ。 # 見落とし

そこでSSP1STATbits.D_nAを確認してみたらみごとに0(アドレス)が返ってきた。
1度空読みしてから再度SSP1IFをクリアすれば次の受信を待つそうだから、やってみることに。

……ずっとアドレスが送られ続ける。ボタンにチャタリング対策を施していないので、
紛らわしいため実装する。(ボタンを押したら待機するだけ)

どうやらデータの送信がうまくいっていないようだ。
> The MSSPx module generates an interrupt at
the end of the ninth clock cycle by setting the
SSPxIF bit.
初めのデータがかけていたのはスタートコンディション送信後に
SSP1IFをクリアして、その後アドレスを送信した後、何も待機せずにそのままデータを送ってしまったのが問題なようだ。

アドレスを送り終えたときにSSP1IFがセットされるため、そこまで待機する。
while(!SSP1IF);
を追加した。ついでにACKの確認も追加して、

SSP1IF = 0;
SSP1BUF = id << 1; // アドレス+W指定
while(!SSP1IF); // アドレス送信待ち

if(SSP1CON2bits.ACKSTAT){
    return; // 送信失敗
}
とした。

データの送信時も同様に、データを格納したらSSP1IFがセットされるまで待機し、それを繰り返すことに。
NO-ACKが返ってきたら再度データを送る仕様にした。

while(data < end_data){
    SSP1IF = 0;
    SSP1BUF = *data;
    while(!SSP1IF);
    if(SSP1CON2bits.ACKSTAT){
        continue; // 送信失敗
    }
    data++;
}

スレーブ側では、データの受信にアドレスかそうでないかで振り分けて判断した。
if(SSP1STATbits.D_nA){
    g_buffer[g_buffer_position++] = SSP1BUF;
}
else{
    data = SSP1BUF;
    g_buffer_position = 0;
}

ここで、空読みとして、適当なレジスタにSSP1BUFを読み込ませる処理があるが、これがないと受信されない。
なお、SSP1BUF = 0;のように適当な値を代入することでも動作が確認できたため、こちらを採用した。

また、SSP1CON3のストップコンディションの際の割り込みを有効にしていなかったため、
送信の終了判定が出来ず苦労していた。これも初期化時に有効にして、割り込み内でSSP1STATbits.Pをセンスして判定した。
結局次のようなプログラムになった。

if(SSP1STATbits.P){
    g_callback(g_buffer, g_buffer_position);
}
else{
    if(SSP1STATbits.D_nA){
        g_buffer[g_buffer_position++] = SSP1BUF;
    }
    else{
        SSP1BUF = 0;
        g_buffer_position = 0;
    }
}

ただしスレーブ受信のみに対応しており、送信の場合はR/Wフラグを確認して動作を割り振る必要がある。

スレーブが送信するプログラムはそのタイミングですでに送るデータが用意されているわけだから、
例えばマスターがデータをくれという命令を出して特定のデータを送信するといったデバイスが考えられる。

TPIP周辺回路では、TPIPから送られるシリアルデータをI2Cで各基板に送り届け、
入力が必要な基板では、必要なタイミングでデータを送るように命令し、取得する。

明日はスレーブ送信とマスタ受信を完成させる予定。
今現在としては、スレーブ送信要求が来たら予め設定した関数ポインタをコールバックし、
その内部で送信関数を使って送信するという流れを予定している。

結局次のようなヘッダファイルになりそうだ。

// マスターモード初期化
void I2C_initMasterMode(void);

// スレーブモード初期化(id)
void I2C_initSlaveMode(char id, void (*callback)(char *data, char size));

// マスターモード送信
void I2C_sendByMaster(char id, char *data, char size);

// マスターモード受信
void I2C_receiveToMaster(void);

// スレーブモード送信
void I2C_sendBySlave(char *data, char size);

// スレーブモード受信
void I2C_receiveToSlave(void);

* パケットにして送るメモ
ところでサーボの制御のためにデータをたくさん垂れ流していくわけだが、
シリアルデータを各基板に送らないといけないわけだから、そのシリアルデータには区切りが存在する。
その区切りを例えば0x7Eとする(フレームデリミタという)。

つまり、0x7E データ 0x7Eをひとかたまりのデータとして送りつける。
もしもデータ中に0x7Eがあった場合は混乱してしまうため、0x7Eを別のデータに変えて、
それが返られたことを示すエスケープオクテット0x7Dを前に挿入することにする。

0x7Eは上から3bit目を反転させて0x5Eとする。つまり、データ中の0x7Eは0x7D 0x5Eに置換される。
ところで0x7D 0x5Eが存在している場合、勝手に0x7Eに置換されてしまって困る。
そこでエスケープオクテット0x7Dもデータ中に存在している場合、
3bit目を反転させた0x5Dに置換して、その前に0x7Dを挿入することにする。
即ち0x7Dは0x7D 0x5Dに置換される。
するとデータ中には0x7Dがエスケープのためだけに存在することになる。
そして、0x7Eはフレームの開始と収量を表すためだけに存在することになる。

(例)
データ : 0x7E 0x7D 0x33 0x7D 0x7E
置換後 : 0x7D 0x5E 0x7D 0x5D 0x33 0x7D 0x5D 0x7D 0x5E

このような1フーレムのデータが来たら、それを適当に解釈して(例えば先頭がアドレス、とか)
他の基板にI2Cで転送する。もしもスレーブ側が送信を必要とする場合は、
そのような命令をフレーム内に記述して(例えばアドレスに送受信の要求を含めるとか)送り返して貰う。

あと明日こそバッテリー試験用基板も作ります;; # バイト終わるまでに出来るといいなー

とはいっても、
(1) PCからテスト要求をRS232Cで出す
(2) PICはそれを受信してテスト(サーボを上下に振り続ける)を開始する。
(3) PCから1秒おきにPICにデータ要求を出し、特定の値を取得する。
(4) PICはアナログ値を取得し、PCに送る。
(5) PCはその値がある値より小さくなったりしたときにテストを終了させる。
(6) PCは記録したデータをtext file等に出力して終了する。

まあできればグラフまで表示させればいいんですけどね。