■競技プログラミング AtCoder Beginner Contest 133 B問題 その3

AtCoder Beginner Contest 133 の B 問題「Good Distance」のプログラムを書いてみました。

 

/*  AtCoder Beginner Contest 133 B - Good Distance
    2019.07.25 by meyon  */

 

#include <stdio.h>
#include <stdlib.h>

 

void err()
{
  printf("Error!¥n");
  exit(1);
}

 

int main()
{
  // input N:point and D:dimension
  int N, D;
  scanf("%d %d", &N, &D);
  if(N<2 || N>10 || D<1 || D>10) {
    err();
  }

 

  // input X:coordinates
  int X[N][D];
  for(int i=0; i<N; i++) {
    for(int j=0; j<D; j++) {
      scanf("%d", &X[i][j]);
      if(X[i][j] < -20 || X[i][j] > 20) {
        err();
      }
    }
  }

 

  // calculate norm
  int ans=0;
  for(int i=0; i<N-1; i++) {
    for(int j=i+1; j<N; j++) {
      int norm=0;
      for(int k=0; k<D; k++) {
        int diff = X[i][k] - X[j][k];
        norm += diff * diff;
      }

 

      // check norm whether integer
      int flag=0;
      for(int k=0; k*k <= norm; k++) {
        if(k*k == norm) {
          flag=1;
        }
      }
      if(flag) {
        ans++;
      }
    }
  }

 

  // display answer
  printf("%d¥n", ans);

 

  return 0;
}

 

err() 関数は Error! を表示してプログラムをエラー終了します。exit() を利用するために stdlib.h をインクルードしています。

点の数 N と次元数 D を入力したら、その数で座標の配列変数を定義します。for 文で座標データを入力します。

calculate norm と check norm whether integer の部分は解説の実装例を参考にしました。ループの条件式がちょっと異なっているのと、差分の絶対値とはせずそのまま差をだしているところが相違点です。

ちなみに diff は差分、norm はベクトルの距離みたいな意味ですね。

実装例のように flag を boolean 型にするには別のヘッダファイルが必要になります。まぁ int 型でも問題ないかなと。

 

提出した結果、AC をいただきました。

今回はちょっと難しかったですけど、考え方さえ分かればなんとかなりそうですよね。たくさん勉強させていただきました。

 


■競技プログラミング AtCoder Beginner Contest 133 B問題 その2

AtCoder Beginner Contest 133 B - Good Distance を考えています。問題は理解できたので、次はプログラムを考えてみようと思います。

 

流れとしては、

 

  1.  点の数、次元の数を入力
  2.  座標データを入力
  3.  Yij を計算
  4.  k2==Yij かどうかを判定
  5.  3. 4. をすべての点間で繰り返す
  6.  4. が真だった数を出力として表示

 

といった感じですが、俺にはどうすればよいのかわからない部分があります。

一つは「3. Yij を計算」について。次元の数 D によって計算する項目数が変わってきます。D によって式を変化させるにはどうすればよいのでしょうか。もう一つは「5. すべての点間で繰り返す」です。漠然と「for 文を使って N、D の数だけ繰り返す」というイメージを浮かべますが、具体的な方法に考えが進みません。

 

今の俺の実力は「考えて悩むよりも真似してみよう」レベル (^_^;) 幸い解説に C++ の実装例がありましたので、これを参考にしてみましょう。

実装例で計算を行なっている部分は以下です。なるほど!と思ったのは太字の部分、( ) を計算し二乗して加算代入していくというところです。これで二つの疑問点は一気に解決できそうです。

 

for (int i = 0; i < N; ++i) {

for (int j = i+1; j < N; ++j) {

  int norm = 0;

  for (int k = 0; k < D; ++k) {

    int diff = abs(X[i][k] - X[j][k]);

    norm += diff * diff;

  }

 

では、入力例 2 の N=3、D=4 の場合で考えてみましょう。

座標は、

 

        1 番目の点 (X00 X01 X02 X03)

        2 番目の点 (X10 X11 X12 X13)

        3 番目の点 (X20 X21 X22 X23)

 

で、配列変数 X[N][D] に格納されています。

最初に 1 番目の点と 2 番目の点の距離の二乗 Y01 を求めます。

 

Y01 =(X00-X10)2+(X01-x11)2+(X02-X12)2+(X03-X13)2

 

次に 1 番目の点と 3 番目の点の距離の二乗 Y02 を求めます。

 

Y02 =(X00-X20)2+(X01-x21)2+(X02-X22)2+(X03-X23)2

 

最後は 2 番目の点と 3番目の点の間です。

 

Y12 =(X10-X20)2+(X11-x21)2+(X12-X22)2+(X13-X23)2

 

これですべての点の間の計算は終わりです。

X[i][k]-X[j][k] とすると、

 

for (i=0; i<N-1; i++) {

  for (j=i+1; j<N; j++) {

    for (k=0; k<D; k++) {

      X[i][k] - X[j][k]

 

といったループを作ればよいことがわかります。

ちなみに、N=10 とすると必要な計算式は、i=0 のときに 9 個、i=1 のときに 8 個、……、i=8 のときに 1 個の計 45 個 (10C2=45) となります。実装例では i=9 までループしますが、次の j のループが偽となって終了するので、結果は変わりません。ループが 1 回空振りするだけですね。

なお、実装例では差分の絶対値をとっていますが、どうせ二乗するのですから不必要だと考えています。

 

さぁて、ようやくアルゴリズムも理解できたので、次回こそプログラムを書いてみることにしましょう。

 


■競技プログラミング AtCoder Beginner Contest 133 B問題 その1

調子に乗った meyon さんはさらに挑戦してみました (^_^;)

AtCoder Beginner Contest 133 の B 問題「Good Distance」です。

 

画像キャプション

 

この問題はピンときました。座標上の 2点の間の距離を求めるのですから、いわゆる三平方の定理ってやつですね。

多次元の場合については俺は知らなかったのですが、問題に示された公式で適用できるのでしょう。基本的な考え方は三平方の定理も同じなんだろうなと想像できます。

 

例 1 を見てみましょう。

N=3 、D=2 ですから、平面 (二次元空間) 上に 3 個の点があるということです。ごく普通に三平方の定理で考えればいいですね。

たとえば 1 番目の点 (1,2) から 2 番目の点 (5,5) までの距離は √( (5-1)+ (5-2)) です。 え〜と式の表示が変ですけど、笑ってこらえてやってください (^_^;)

 

さて、次にこの式の値が整数かどうかを判定しないといけませんが、えーと、どうしましょう? 思いついたのは以下のようなものです。

 

  1. 小数部分を取り出して、それが 0 かどうか。0 なら整数です。
  2. 小数点以下を切り捨てて、それが元の値と同じかどうか。同じなら整数です。

 

でもなんだかねぇ、なぜだかいまいちすっきりしないんですよ…

迷ったのでちょっと解説をカンニングしてみました。すると

 

k2=Yij を満たす k が存在するかを調べれば √ Yij が整数であることを判定できます。

 

とあります。

そうか、平方根というムズムズするような値を判定するんじゃなくて、整数 k を二乗した値が (5-1)2+(5-2)2 と等しいかどうかを調べればよいのですね。それならすべての変数を int 型で扱えます。

ポン、なるほど、目から鱗のアルゴリズム (^_^;)

 

例 2 はどうでしょうか。

N=3 、D=4 ですから四次元空間上に 3 個の点が存在します。四次元空間…… う〜ん、四次元空間を想像することはできませんが、公式に当てはめて計算するだけのことです。

1 番目の点から 2 番目の点までの距離は、

 

Y12=(-3+12)2+(7-1)2+(10-8)2+(2-2)2=121

 

121 は 112 なので真 (整数) です。

2 番目と 3 番目の間は 、

 

Y23=(-2+12)2+(8-1)2+(10-9)2+(3-2)2=151

 

151 は 122 と 132 の間なので偽 (整数ではない) です。

同様に 1 番目と 3 番目の間は、

 

Y31=(-2+3)2+(8-7)2+(9-8)2+(3-2)2=4

 

これは 22 ですから真です。

したがって真が 2 つでしたから、出力は 2 となります。

 

例 3 は直線 (一次元空間) 上に 5 個の点があります。座標はすべて整数ですから、これらの点の間の距離は、計算するまでもなく整数であるとわかります。でも、プログラムでは何次元でも同じように処理できなければなりませんので、公式に当てはめてみましょう。

1 番目と 2 番目の間は Y12=(2-1)2=12 で真です。2 番目と 4 番目の間も Y24=(4-2)2=22 で真です。3 番目と 5 番目の間も Y35=(5-3)2=22 で真です。他の組み合わせもすべて同じく真になります。組み合わせの数は 10  ( 5C2 ) です。

 

さて、これで問題が理解できましたので、次回はプログラムを考えてみることにします。

 


■競技プログラミング AtCoder Beginner Contest 133 A問題

めげてないで努力してみましょう。

コンテストをよく見てみると「AtCoder Beginner Contest」というのがありました。先日飛びついたのは「AtCoder Grand Contest」でしたので、そりゃ難しいに決まってます (^_^;)

 

AtCoder Beginner Contest 133 の A 問題「T or T」です。

 

問題文
私たちは N 人で旅行しようとしており、その交通手段として電車とタクシーがあります。

電車を使うと 1 人あたり A 円かかります。

タクシーを使うと N 人で B 円かかります。

全員の交通費の合計は最小でいくらになるでしょうか。

 

なんか算数の問題っぽくってよさげなんですが、またしても問題の意味がわからない。「全員の交通費の合計は最小でいくらになるでしょうか」ってどーゆーこと?

 

例 1 を見てみましょう。人数 N が 4 人、電車 A が 2 円でタクシー B が 9 円とすると、電車が 4 人× 2 円で 8 円、タクシーが 4 人で 9 円ですから、全員の交通費の合計の最小値は 8 円、だそうです。や、安すぎる (^_^;)

えーとつまり、「交通費が安くなる交通手段はどっちか」ってことか。20 人いたらタクシーだと 5 台必要だから… なんてこと考えなくていいのね?

うぅ、なんかあまりに現実離れしていて頭がついていかない… orz

 

例 2 では、N=4 A=2 B=7 ですから、電車が 8 円、タクシーが 7 円で、出力は 7 。

例 3 では、N=4 A=2 B=8 で、電車もタクシーも同額の 8円で、出力は 8 となります。

 

わかりましたよ。こうしましょう。

 

  1. 数値の入力
    scanf() で変数にそれぞれ人数、電車の運賃、タクシーの運賃を入力します。
  2. 範囲の判定
    入力値が範囲外かどうかを判定して、範囲外の時は Error! を表示してプログラムを終了 (return 1;) します。たぶん、この制約の範囲内でプログラムを考えればいいよってことで、エラー処理はしなくてもいいみたいですけど。
  3. 最小値の計算と抽出
    最初は安いほうの運賃で分岐してどちらかを表示させる方法を考えたのですが、処理部分と表示部分は分けたほうがわかりいいよねってことで分けました。
    まず電車賃を最小値 lowestFare としておき、次にタクシー代と比較して、もしタクシー代の方が安ければ lowestFare を更新するという単純な方法です。
  4. 最小値の表示
    printf() で最小値 lowestFare を表示します。

 

で、こんなふうになりました。

 

/*  AtCoder Beginner Contest 133 A - T or T
    2019.07.24 by meyon  */

 

#include <stdio.h>

 

int main()
{
  // data input and judgment
  int N;    // number of people
  int A;    // train fare
  int B;    // taxi fare
  scanf("%d %d %d", &N, &A, &B);
  if(N<1 || N>20 || A<1 || A>50 || B<1 || B>50) {
    printf("Error!¥n");
    return 1;
  }

 

  // extract the lowest fare
  int lowestFare = N * A;
  if(lowestFare > B) {
    lowestFare = B;
  }

 

  // display the lowest fare
  printf("%d¥n", lowestFare);

 

  return 0;
}

 

なんだかね、やれそうな気がしてきましたよ (^_^;)

 

2019.07.26 追記

過去問題でも提出できることを知りました。

で、提出してみましたら、結果「AC」でしたよ。めでたい \(^o^)/

 


■競技プログラミング過去問題をやろうとしてみた…

やってみたんじゃなくて、やろうとしてみて、門前でめげたとゆ〜話 orz

 

C 言語を勉強しようと思い、なにか例題はないかなぁと探していたら、いいのがありました。

AtCoder:競技プログラミングコンテストを開催する国内最大のサイト

さっそく過去の問題に挑戦してみましょう。

 

7月14日の AtCoder Grand Contest 035 の A 問題「XOR Circle」です。

 

問題文
すぬけ君は N 枚の帽子を持っています。i 枚目の帽子には整数 ai が書かれています。

N 頭のラクダが円環状に並んでいます。 すぬけ君はそれぞれのラクダに 1 枚の帽子を被せようとしています。

どのラクダについても以下の条件が成立するような帽子の被せ方が存在するならば ' Yes ' を、そうでなければ ' No ' を出力してください。

・両隣のラクダが被っている帽子に書かれた数のビットごとの排他的論理和が自身の被っている帽子に書かれた数と等しい

 

すぬけ君って (^_^;) 「競技プログラミング_用語 - projecthikky @ ウィキ - アットウィキ」によると「AtCoder社の社員のレッドコーダーsnukeさんのこと」だそうです。

 

しかし、えっと、あの〜、なんてゆ〜か、問題の意味すらわかりません orz

 

だいたい、すぬけ君はなぜラクダに帽子を被せようとしているわけ? その帽子に書かれた数の排他的論理和が云々ってなんの意味があるん (?_?) 

あ〜そうか、なんか小難しい数値の処理をラクダと帽子に例えたってことね。

登場人物の名前といい、たとえ話の奇想天外さといい、俺みたいな一般人が容易に足を踏み込めそうにないオタクなにおいがしてくる… (^_^;)

 

閑話休題

 

最も理解できなかったのは「自身の被っている帽子」ってところ。帽子はラクダに被せるんでしょ? すぬけ君の被っている帽子ってなによ (?_?) ―― 考えること小一時間、気が付いた。「自身」というのは両隣のラクダに挟まれたラクダのことね。「自身」というからてっきり人だと思った。

つまりこういうこと。

あるラクダの両隣のラクダが被っている帽子に書かれた数のビットごとの排他的論理和が、そのラクダ自身の被っている帽子に書かれた数と等しい」

 

例 1 より、帽子の数 N を 3 、それぞれの帽子に書かれている数字 a を 1 、2 、3 とします。

あるラクダ自身の帽子に書かれた数が 1 だとすると、右隣のラクダの帽子に書かれた数が 3 で、左隣のラクダの帽子に書かれた数は 2 です。右隣の 3 を二進数表示すると 0b11 、左隣の 2 は 0b10 です。それらのビットごとの排他的論理和は 0b01 になります。ラクダ自身は 0b01 で、求めた値と等しくなりますから、与えられた条件は「真」です。

帽子に書かれた数が 2 のラクダについても、両隣のラクダの帽子に書かれた数 1 と 3 のビットごとの排他的論理和は 0b10 になりますから「真」、3 のラクダについても同様に「真」です。

ということで、「どのラクダについても条件が成立」したので ' Yes ' を出力します。

 

では例 2 はどうでしょう。N = 4 、a = 1 2 4 8 とします。

ラクダ a=1 (0b0001) の右隣のラクダを a=8 (0b1000) 、左隣のラクダを a=2 (0b0010) とすると XOR は 10 (0b1010) ですから「偽」です。

両隣を 8 と 4 にしてみても、2 と 4 にしてみても「偽」となり、a=1 について条件を満たすような帽子の被せ方は存在しません。

「どのラクダについても条件が成立する」ことが条件ですから、これ以上他のラクダを検討してみる必要はないでしょう。この場合は ' No ' を出力します。

 

ということで、ようやく問題の意味が理解できました。

 

では問題に従って 3 ≦ N ≦ 10、0 ≦ ai ≦ 109 について考えてみましょう …

 

わ、わっかりませ〜ん orz

 

ラクダの頭数が 10 万頭で、帽子に書かれる数は 10 億までの範囲のラクダの頭数分の個数。帽子に書かれた数がすべて異なるといった条件はありませんから、同じ数が書かれた帽子もありうると… あ〜、力業で総当たりするにしても、どうすればよいものやらさっぱり (^_^;)

つまりこれは、C だのなんだの言う前に、いわゆる「アルゴリズム」ってものをしっかり考えろというとても基本的な命題なのだと、改めて気が付いた meyon さんでした。

 


■電源ユニットに端子板を付ける

余っていたパソコンの電源ユニットを処分しようとしたんだけど、ふと、これ電源装置として使えるよなぁと思ったわけ。で調べてみました。

 

出力は +12V が二系統、+5V、+3.3V が各一系統と -12V 一系統。その他にスタンバイ用の +5VSB があります。

+5VSB は電源 ON で常に 5V が出力されていますが、他は出力していません。PS_ON という信号線を LOW に落とすと冷却ファンが回りだし、すべての出力が ON 。出力が ON になると PWR_OK が HIGH になります。

その他に 3.3Vsence というラインがありますが、これは +3.3V の出力検出用で、マザーボードに挿入するメインコネクタで +3.3V 出力に繋がっていました。

 

参考:ニプロン 製品情報 電源事典 Q&A Q. ATX電源_EPSのピンアサインについて教えて下さい。

 

 

電源ユニットからは、マザーボードや周辺機器を接続するために、さまざまな形のコネクタが付いたハーネスがたくさん出ています。これらを取り外して、出力と制御用に端子板を取り付けることにします。


電源ユニット

 

 

カバーを開けると、基板の出力部周辺はこんな感じ。同じ場所から何本もの電線が束になって出ていることがわかります。


出力線のようす

 

 

基板をひっくり返してみるとこんな感じ。大量のハンダが盛られていて、外し甲斐がありそうです (^_^;)


基板裏面のようす

 

 

電線を取り外しましたが、けっこう大変な作業でした。ハンダを取るとき、周辺の抵抗器などのハンダまで溶かしたり、パターンをショートしたりしないように注意が必要です。

信号線など一本だけ出ているところはそのまま残しています。


電線を取り外した基板

 

 

新たに出力線をハンダ付けし、ケースに戻します。


出力線を取り付け

 

 

ケースに端子板取付用の穴あけをします。ボール盤があればもっときれいにあけられるんですけどねぇ (言い訳 ^_^;)


ケースに端子板用の穴あけ

 

 

端子板を取り付け、出力線をハンダ付けして組み立てたら、完成です。


完成

 

 

端子板は以下のようになっています。3.3Vsence は No.4 端子に接続してあります。

 

No. 1 2 3 4 5 6 7 8 9 10
出力 12V(1) 12V(2) 5V 3.3V GND GND PS_ON 5VSB PWR_OK -12V

 

今回は 10pin の端子板を取り付けたのですが、これだと GND 端子が二つしか取れません。GND 端子は余裕があったほうが使いやすいので、12pin の端子板にしたほうがよかったかなと思っています。あと、端子板の表示をつけないといけませんね。

 


■ArduinoでFizzBuzzしてみた

俺はプログラミングもほとんど初心者に等しいので、そっちの方をもう少し勉強しようと思ってます。

Arduino スケッチは C 言語をベースにしているということなので、C 言語も合わせてやっていきましょう。てなことを考えながら、とりあえず何か書いてみるなら FizzBuzz かなとか (^_^;)

 

FizzBuzz とは、まぁ誰でも知っていると思いますけど、50 まで数えて 3 の倍数のときアホになるというやつです。… じゃぁなくて、100 までカウントして 3 の倍数のとき Fizz 、5 の倍数のとき Buzz、さらに 3 と 5 の倍数のときは FizzBuzz と表示させるといったプログラムのことです。こいつを Arduino でやってみます。

ちょうど 7セグメント LED を使ってみていたので、スタートボタンを押したら 0 から 99 までカウント表示させることにしましょう。3 の倍数のときは FZ 、5 の倍数では BZ 、3 と 5 の倍数のときは FB と表示させることにします。

 

回路図は以下です。特段変わったところはありません。

Arduino の D9 にタクトスイッチをつなぎ、これを ON するとカウントをスタートさせます。

アノードコモンの 7 セグメント LED 表示器が 2個手元にありますので、これをシフトレジスタ SN74LS164 で表示させます。デジットドライブはこれも手持ちの 2N3906 を使用しています。2SA1015 とかでもいいですね。FET でもかまいません。持っているものを適当に使いましょう。でも Arduino で直接駆動するのはちょっと無理があります。

シフトレジスタと LED の接続は、7ビットのデータを LSBFIRST (最下位ビットから送出) させるつもりなので、Qa の出力を未接続にしています。このあたりはプログラムと整合させないとうまく表示できませんので注意します。

 


DizzBuzz_回路図

 

 

スケッチのフローチャートです。

スイッチの ON を検出して 0 から 99 までカウントします。それを 7 セグメント LED で 2 桁表示させます。十の位が 0 のときは表示しないようにしています。2 桁を 1 ミリ秒ずつ表示して 300 回繰り返すので、約 600 ミリ秒ごとにカウントしていくことになります。

その値の倍数かどうかは剰余が 0 かどうかを判断します。3 と 5 の倍数ということは 15 の倍数ということです。ここが FizzBuzz の肝ですね。それぞれの判定結果に従って、表示する値を変えています。

ん〜、なんかもっとすっきりできそうな気がするけど、いまの俺のレベルではこんなもんでしょ (^_^;)

 


FizzBuzz_フローチャート

 

 

スケッチです。

じつは、関数の引数とか戻り値とか初めて使ってみたんですよ (^_^;)

 

// Fizz Buzz on Arduino  2019.06.16 meyon

 

/*  スイッチを押下すると 0 から 99 までカウントする。
    3 の倍数のとき FZ 、5 の倍数のとき BZ 、
    15 の倍数のとき FB と表示させる。
*/

 

const byte segmentData[14] = {
  B1111110, // 0
  B0110000, // 1
  B1101101, // 2
  B1111001, // 3
  B0110011, // 4
  B1011011, // 5
  B1011111, // 6
  B1110000, // 7
  B1111111, // 8
  B1111011, // 9
  B1000111, // F
  B0011111, // B
  B1101100, // Z
  B0000000  // blank
};

 

const int startPin = 9;
const int driverPin[2] = {10, 11};
const int clockPin = 12;
const int dataPin = 13;

 


int judgeNumber(int value) {
  int result;
  if(value == 0) {
    result = 0;
  } else if(value % 15 == 0) {
    result = 15;
  } else if(value % 5 == 0) {
    result = 5;
  } else if(value % 3 == 0) {
    result = 3;
  } else {
    result = value;
  }
  return result;
}

 


void displayNumber(int value) {
  int digit[2];
  if(value == 15) {
    digit[0] = 11;        // B
    digit[1] = 10;        // F
  } else if(value == 5) {
    digit[0] = 12;        // Z
    digit[1] = 11;        // B
  } else if(value == 3) {
    digit[0] = 12;        // Z
    digit[1] = 10;        // F
  } else {
    digit[0] = value % 10;
    if(value / 10 == 0) {
      digit[1] = 13;      // 10 digit is blank
    } else {
      digit[1] = value / 10;
    }
  }

 

  int j=0;
  while(++j < 300) {      // Display cycle
    for(int i=0; i<2; i++) {
      shiftOut(dataPin, clockPin, LSBFIRST, ~segmentData[digit[i]]);
      digitalWrite(driverPin[i], LOW);
      delay(1);
      digitalWrite(driverPin[i], HIGH);
    }
  }
  return;
}

 

 

void setup() {
  pinMode(startPin, INPUT);
  pinMode(driverPin[0], OUTPUT);
  pinMode(driverPin[1], OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  digitalWrite(driverPin[0], HIGH);
  digitalWrite(driverPin[1], HIGH);
}

 


void loop() {
  if(digitalRead(startPin) == LOW) {
    for(int i=0; i<100; i++) {
      int value = judgeNumber(i);
      displayNumber(value);
    }
  }
}

 

 

ついでにブレッドボードです。

7 セグメント LED がピンが左右についている品物なので 2桁並べて置けません。ご愛嬌です。

回路図にはありませんが、IC の電源には 0.1μF のコンデンサをつけてます。またブレッドボードの電源ラインには 10μF 程度の電解コンデンサを置くようにしています。
 


FizzBuzz_ブレッドボード

 

 

電源が写真にはありませんが、12V の AC アダプタの出力から 3 端子レギュレータで 5V を作っています。Arduino の電源は 12V を Vin に供給しています。ただ、USB ケーブルを挿したまま AC アダプタを切っちゃうと Vin から 5V が逆流してしまうので、12V ラインにダイオードを入れて阻止しています。まぁ小ネタですけど。

 


■Arduino Nano 互換機を買ってみた

Amazon で「HiLetgo® 3個セット Mini USB Nano V3.0 ATmega328P CH340G 5V 16M マイクロコントローラーボード Arduinoと互換」を購入しました。結果、何の問題もなく期待通り動作してくれました。

 

廉価な互換機ということなので、Uno との違いもさることながら、正規品との違いも確認しておきたいとググってみたのですが、どうもなにやら良い印象がない (^_^;) 

レビューでも数個に 1個は動作しないとか、USB シリアル変換チップ CH340 用のドライバをインストールしないといけないとか、その CH340 が偽物であるとか、Old Bootloader を選択しないと書き込みできないとか… 使ってみたというサイトを見ても同様の情報が多く、また中国からの配送なので時間がかかるとか、まぁ導入時は多少苦労するかなと覚悟をしていました。

 

が、そんな懸念は一切無用。Ubuntu 18.04.2 LTS のパソコンで、次の設定で動作しています。

 

ボード:Arduino Nano

プロセッサ:ATmega328P

シリアルポート:/dev/ttyUSB0

 

CH340 のドライバのインストールは不要です。ブートローダも Old ではないほうで良いので、新しいものになっているようです。ちなみに CH340 の真贋の判断基準となるとか言われているマークは底の平らな大きな丸でした。見た目も気になるようなところは何もなく、粗悪な安物といった感じはありません。


Arduino nano 互換機

 

USB ケーブル (A to miniB) とその他  1点とともに、注文から約 24時間で到着しました。さすが Amazon's Choice です。寸法はわかっていましたが、実物は思っていた以上に小さく感じました。A コネクタと変わらないですね。

 

まず、3個すべてに Blink を書き込んで正常に動作することを確認しました。

付属のピンヘッダを取り付けて、以前作った 3x3x3 LED Cube につないでみました。これも全く問題なく動作しています。このサイズの基板で実装できそうですね。


3x3x3 LED Cube with 互換機

 

 


■ファンクションボタンの検出

最近よく見かける、上下左右にメニューボタン、中央に決定ボタンがあるような、なんていうのか名前を知らないのですが、ファンクションボタンを作ってみました。

 

タクトスイッチ 6個を「up」「down」「right」「left」「set」「reset」のファンクションボタンとし、どのボタンが押されたかを検出します。今回の肝は電圧分圧方式でのボタン検出で、押したボタンによって入力電圧が変化するという仕組み。ググると考え方や抵抗値の計算方法が出てきますので参考にしてください。

二つ以上のボタンを同時に押した場合は優先順位がありますので、今回は「reset」を最優先のボタンとして並べてみました。

計算上の分電圧と実際の入力値は異なりますので、Arduino の入力値を実測してボタン判断のしきい値を決めています。

どのボタンが押されたかを表示するために、手元にあった 7 セグメント LED (アノードコモン) をシフトレジスタで表示させています。シフトレジスタは前に LED キューブで使っていた SN74LS164 です。こいつはストレージレジスタがありませんので、ドライブ用にトランジスタ 2N3906 を付けました。これによりデータ送信中は LED を表示させないようにしています。セグメントの電流は 7mA 程度、2N3906 は最大コレクタ電流 200mA ですので十分使えます。

 

回路図です。


function-switch 回路図

 

ブレッドボードはこんな感じ。実際はこれを時計方向に 90° 回して実験していますので、右上のタクトスイッチは上下左右に 4 個のボタンと左下に reset 、右下に set という配置になってます。まぁどんな並びでも関係ないんですけど (^_^;)  押したボタンに応じて数字が表示されるという単純なものです。


function-switch ブレッドボード

 

 

フローチャートとスケッチです。ボタン判断のしきい値がフローチャートとスケッチで異なっていますが、最終的にはスケッチの値にしています。

 


画像キャプション

 

// function-switch 2019.06.06 meyon

 

byte segmentData[7] = {
  B1111110, // 0
  B0110000, // 1
  B1101101, // 2
  B1111001, // 3
  B0110011, // 4
  B1011011, // 5
  B1011111  // 6
};

int driverPin = 11;
int clockPin = 12;
int dataPin = 13;
int displyNumber = 0;


int buttonPressed() {
  int inputVoltage = analogRead(A0);

  if (inputVoltage > 947) {
    return 0; //off
  } else if (inputVoltage > 779) {
    return 1; //set
  } else if (inputVoltage > 602) {
    return 3; //up
  } else if (inputVoltage > 436) {
    return 5; //right
  } else if (inputVoltage > 268) {
    return 4; //down
  } else if (inputVoltage > 90) {
    return 6; //left
  } else {
    return 2; //reset
  }
}


void displyButtonNo() {
  digitalWrite (driverPin, HIGH);
  shiftOut(dataPin, clockPin, MSBFIRST, ~segmentData[displyNumber]);
  digitalWrite (driverPin, LOW);
  return;
}


void setup() {
  pinMode (driverPin, OUTPUT);
  pinMode (clockPin, OUTPUT);
  pinMode (dataPin, OUTPUT);

  Serial.begin(9600);
}

 

void loop() {
  Serial.println(analogRead(A0));
  
  if (buttonPressed() != 0) {
    displyNumber = buttonPressed();
  }
  displyButtonNo();
}

 

例によって入力値の確認のためにシリアル出力を使ってます。

シフトレジスタもドライブ用トランジスタも LOW で点灯になります。これ勘違いするとすべてがおかしくなってしまいますので (^_^;)

 


■押しボタンスイッチのアクションの検出

Arduino を使って、押しボタンスイッチの入力アクションの検出を行なってみました。

アクションは「シングル (一回押し) 」「ダブル (二回押し) 」「ホールド (長押し) 」の三種類で、これらを一つのタクトスイッチから検出し LED の点灯・点滅・消灯を制御します。

ググってみると、一定の時間内にスイッチの状態を確認して OFF/ON が変化する時間や回数を調べる方法がとられているようですので、その方法で自分なりに考えてみることにします。

 

 

ますはアクションの検出について。

スイッチに入力があったら、そこから 50ミリ秒毎に 10回チェックを行い、その間にあった変化の回数をカウントすることにします。「シングル」では変化は 1回だけです。「ホールド」では変化はありません。「ダブル」の場合は 2回目の変化があった時点で判断します。


アクションの検出

 

 

次に回路図です。

難しいものではありません。タクトスイッチの入力と、出力で LED を駆動するだけです。

入力ピンの電流を確認してみると HIGH でも LOW でも 0.7mA 程度でしたので、プルアップでもプルダウンでもどちらでもよさそうです。今回は ON 時に HIGH となるようプルダウンとしました。

出力も HIGH 時に LED が点灯するようにしました。もちろん電流値をきちんと確認して Arduino で直接駆動してもかまいませんが、これぐらいの単純なスイッチング回路を作るぐらいは屁でもありませんから (^_^;)


回路図

 


大まかなフローチャートです。

グーグル先生たちはこういうのを書かないのか、あまり見ませんね (^_^;)  上手ではありませんが、考えを整理するためにも簡単に作ると具合がいいです。


フローチャート(全体)


「入力待ち」では、スイッチが押されたかどうかを検知します。待つためにここでプログラムを止めてしまうと LED の点滅の制御ができなくなるので、一度検知したら次の処理を行なってからまた戻ってくるようにします。

スイッチが押されたら「変化を調べる」処理をします。前述のように、一定の時間内にスイッチの状態が変化した回数を調べます。

変化した回数をもとに、「LED表示」で点灯・点滅・消灯の制御を行います。点滅は 150ミリ秒を経過することに点灯・消灯を切り替えます。

 

「入力待ち」はこんな感じです。

入力ピンの状態を読み込んでスイッチが押されたら state_of_button をセットします。押されなければ何もしません。


フローチャート(入力待ち)

 

スイッチが押されたら「変化を調べる」を実行します。押されていないときは何もしません。

50ミリ秒待ってからスイッチの状態が変化 (ON から OFF、または OFF から ON) しているかを調べます。変化なければ、また50ミリ秒待って調べます。これを 10回、都合 500ミリ秒間行ないます。

スイッチの状態が変化していたら number_of_changes を加算します。変化の回数が 2 になれば「ダブル」が確定しますので、ループを抜けます。これで number_of_changes は「シングル」が 1 、「ダブル」が 2 、「ホールド」は 0 となります。

ちなみに、最後の elapsed_time リセットは LED 点滅周期タイマーのスタートです。


フローチャート(変化を調べる)

 

最後は「LED 表示」です。

number_of_changes 値に従って switch case で分岐します。点滅制御は millis() 関数を利用して経過時間を調べ、150ミリ秒を経過していれば light 値を反転させて点灯・消灯を繰り返すという方法をとります。L チカでよくある delay を使う方法では、プログラムがここで止まってしまうのでうまくありません。


フローチャート(LED表示)

 

 

ということでできたスケッチは以下です。

// button-status.ino 2019/05/31 meyon

 

int inputPin = 2;
int outputPin = 13;
boolean state_of_button = 0;
boolean state_of_currently = 0;
int number_of_changes = 0;
unsigned long elapsed_time;
boolean light = 0;

 

void wait_input(){
  state_of_currently = digitalRead(inputPin);
  if(state_of_currently == 1){
    state_of_button = 1;
    number_of_changes = 0;
  }
  return;
}

 

void detect_changes(){
  if(state_of_button == 1){
    for(int i=0; i<10; i++){
      delay(50);
      state_of_currently = digitalRead(inputPin);
      if(state_of_currently != state_of_button){
        number_of_changes ++;
        if(number_of_changes >= 2){
          break;
        }
        state_of_button = state_of_currently;
      }
    }
    while(state_of_currently == 1){
      state_of_currently = digitalRead(inputPin);
    }
    state_of_button = 0;
    elapsed_time = millis();
  }
  return;
}

 

void light_led(){
  switch(number_of_changes){
    case 0:
      light = 0;
      break;
    case 1:
      light = 1;
      break;
    case 2:
      if(millis() - elapsed_time > 150){
        light = !light;
        elapsed_time = millis();
      }
  }
  digitalWrite(outputPin, light);

  return;
}

 

void setup() {
  pinMode(outputPin, OUTPUT);
  pinMode(inputPin,  INPUT);

 

//  Serial.begin(9600);
}

 

void loop() {
  wait_input();
  detect_changes();
  light_led();

 

//  Serial.print(state_of_currently);
//  Serial.print(state_of_button);
//  Serial.println(number_of_changes);

}

 

コメントアウトしてあるシリアル出力はデバック時に変数の値を確認していたものです。このように適当な位置に Serial.print を入れると、その場所で変数がどうなっているかが見えるようになります。うまく動かないときに重宝しますね。

 

ということで、スイッチアクションで制御を切り替えることができるようになりました。

 


<< | 2/61PAGES | >>

■calendar

S M T W T F S
1234567
891011121314
15161718192021
22232425262728
2930     
<< September 2019 >>

■search this site.

■recommend

毎日貯まるポイントサイト ECナビ

■recommend

* 楽天ROOM *

■Twitter

■recommend

■recommend

■selected entries

■categories

■archives

■recent comment

■recent trackback

■links

■profile

■others

■mobile

qrcode

■powered

無料ブログ作成サービス JUGEM