PC and PC troubles

07/23/2017

Arduino を使ってexercise の音楽と時間管理をしたい

Aruduinoを使ってエクササイズの音楽と時間管理をしてみたい。
先ずは、Arduinoで時間管理が出来るのか・・・試してみなければ・・・。

《リモコンで、
こんなことがしてみたい
①いわゆるラジカセの電源ON/OFF
②音楽の演奏順や演奏番号を指定
③ボリュームの増減や音量の指定

僕が現在想定しているラジカセは「東芝製 SD/USB/CDラジオ TY-CRX71(W)」で、メディアはSDカードを考えている。

この機種は、SDカードを挿入しておいて本体の電源を入れても、SDカードが認識されないので、確認不十分だが、リモコンで出来る操作はOFFのみかもしれない。
リモコンによるプログラムの手順などを確認する必要がある。
リモコンにある音量ボタンは「-/+」だけなので、Arduinoでどのようにすれば音量を変えられるか、確認する必要がある。

以上のような、確認不十分な課題があるが、とにかくArduinoで時間管理できれば、いわゆるラジカセは別のものに変更して、よりコントロールの行いやすいものにするという方法もあるだろう。

Img_20170723_171122 それで、Arduinoを使って時間管理するスケッチを作ってみることにした。

時間制御関数を調べてみると、Arduinoにはミリ秒やマイクロ秒で制御できる関数がある。この関数を使えば、開始時間からの制御が出来る。
時間関数については、Arduinoがスタートしてからの時刻のずれが問題にならないようにするため、

 millis() :Arduinoが起動してからの時間(ミリ秒)を返す

を使うことにした。また、時間をチェックするところは、ユーザー関数を作成した。

次に、エクササイズの決められた実行時間(1分間)と休憩時間(1分間)を、ほぼ正確に繰り返し(エクササイズ8回と休憩7回後、5分間の休憩を)制御するようにするため、繰り返しした際の条件判定・制御文をどうすれば良いか、随分迷った。
これに関連する制御文は、
 ① (if と) for を組み合わせた制御文
   for(初期処理;条件;変更処理){処理群}

 ② while 制御文
   while(条件){処理群}

 ③ do-while 制御文
   do{処理群}while(条件)

僕は、上記の制御文を試してみて、エクササイズの繰り返し判定には①を、時間の判定には③を用いて下記のようなスケッチを作成した。

リモコンの制御部分は、ここでは print文としてスケッチを作成してある。後で制御コードを調べた後に組み替えたい。

処理速度が速くないArduinoなので、Arduinoは2台の構成を考えている。
・ 1台は親機で、時間管理とラジカセの添付リモコンに代り、リモコンデータを送信する。
・ もう一台は、子機で、親機からの別のリモコンデータを受信して、MP3データで作成された音声を発音する。これは、以前のブログで紹介した記事「TVリモコンを使ってArduinoを喋らせる」を参考に作ることになると思う。

これらの詳細は、次回以降明らかに出来ると思う。ここでは、自分用のメモとして、下記Sketchをここに書いておく。

《時間管理のSketch案》

// Arduino を使った時間管理のための sketch
// 音量位置 Vol1:min, Vol2:med, Vol3:max
//
long i=0;
int  n=0;
int  m=0;
unsigned long atime;  // 告知時刻
unsigned long    tc;  //
unsigned long   tc2;  //
unsigned long t0 = millis();  // 時刻の初期化

// セットアップ
void setup(){
  Serial.begin(9600);
// CD  Playerへの指示
  Serial.println("Player の 電源ON");
  Serial.println("Player の 音楽を選択する");
  Serial.println("Player の Play!");
  
// CD  Playerへの指示
  Serial.println("Player の 音量 Vol1 へ");
  
  atime =15;
  
// ここから カウンター m=0 から 3回ループする
for (m=0; m<3; m++){
  Serial.println(" ");
  Serial.print("m = ");
  Serial.println(m+1);
  
// MP3 Playerへの指示:15秒前と告知する
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  Serial.println(" ");
  Serial.println("スタート15秒前です!");

  atime = atime + 5;  // 5秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  Serial.println(" ");
  Serial.println("10秒前です!");
  
// ここから カウンター n=0 から 8回ループする *****基本の運動ループ部分*****
for (n=0; n<8; n++ ){
// CD  Playerへの指示
  Serial.println("Player の 音量を Vol1 へ");
  
  Serial.print("n = ");
  Serial.println(n);
  
  atime = atime + 5;  // 5秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  Serial.println(" ");
  Serial.println(" 5秒前です!");
  
  atime = atime + 2;  // 2秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 440);
  Serial.println(" ");
  Serial.println(" 3秒前です!");
  noTone(8);
  
  atime = atime + 1;  // 1秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 440);
  Serial.println(" ");
  Serial.println(" 2秒前です!");
  noTone(8);
  
  atime = atime + 1;  // 1秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 440);
  Serial.println(" ");
  Serial.println(" 1秒前です!");
  noTone(8);
  
  atime = atime + 1;  // 1秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 880);
  Serial.println(" ");
  Serial.print(n+1);
  Serial.println(" 回目の 1分間エクササイズ Go!!!!!!!!!!!!!!");
  noTone(8);
// CD  Playerへの指示
  Serial.println("Player の 音量を Vol3 へ");
  
  atime = atime + 55;  // 55秒後に告知
  CountTimer( atime);
// CD  Playerへの指示
  Serial.println(" ");
  Serial.println("Player の 音量を Vol1 へ");

  atime = atime + 2;  // 2秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 440);
  Serial.println(" ");
  Serial.println(" 3秒前です!");
  noTone(8);

  atime = atime + 1;  // 1秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 440);
  Serial.println(" ");
  Serial.println(" 2秒前です!");
  noTone(8);
  
  atime = atime + 1;  // 1秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 440);
  Serial.println(" ");
  Serial.println(" 1秒前です!");
  noTone(8);
  
  atime = atime + 1;  // 1秒後に告知
  Serial.print("atime=");
  Serial.println(atime);
  CountTimer( atime);
  tone(8, 880);
  Serial.println(" ");
  Serial.print(n+1);
  Serial.println(" 回目の 1分間 Brake!!!!!!!!!!!!!!!!!!!!!!!");
  noTone(8);
// CD  Playerへの指示
  Serial.println("Player の 音楽を変更する");
  
  atime = atime + 50;  // 50秒後に告知
}
// 5分間休憩の時間をセット
  atime = atime - 50;
  atime = atime + 5*60 - 15;  // 4分45秒後に告知
  Serial.println(" ");
  Serial.print(m+1);
  Serial.println(" 回目の 5分間 Brake!!!!!!!!!!!!!!!!!!!!!!!");
// CD  Playerへの指示
  Serial.println("Player の 音量を Vol2 へ");
}
// CD  Playerへの指示
  Serial.println("Player をストップさせる");
  Serial.println("Player の電源 OFF");
}
void loop() { }
int CountTimer(unsigned long tsec)
{ // 時間をカウントチェックするためのユーザー関数
  // t0   : 正となる時刻
  // tsec : このサブプログラムで秒読みする時間

  int i=0;  // millis() を読むカウンター
  unsigned long tc;  // tc  : 現時刻
  unsigned long t2 = tsec*1000;    // t2  : t0からの tsec秒経過した時刻

  tc = millis();
  
  while( tc < t2 ){
  tc = millis();
  i=i+1;
  }
 return;
}

07/18/2017

Arduino で Sonic Radar を作る

Img_20170629_220931 Arduinoと超音波距離センサー「HC-SR04」(秋月電子から購入) を使った電子工作の中に面白いものを見つけたので、作ってみた。

これは、「Sonic Radar」というもので、デバイスから超音波を発して、跳ね返ってきた音を受信し、その時間で距離を計測するというものである。PCの助けを借りると、モニター画面上に映し出された表示結果は、レーダー画像を見ているような雰囲気がある。
参考にさせて戴いたURLは、
「ROBOTIC
TUTORIALS.COM」の中の、Ultrasonic based RADAR である。

ハードウェアの組立は、結線箇所も少なく簡単に出来る。ハードウェアの組立に関しては、多くの他のブログでも紹介記事があるので、参考にするとよい。
しかし、PCの画面に結果を表示するためには、Processingというソフトウェアの助けを借りなければならない。
僕は、Processingに関する知識が今は無いので、ネット上にあるものを拝借して出来るものをいろいろ探し回ってみた。私の組上げたシステムで表示できるもは、なかなか見つからなかった。ようやく見つけたのが、このURLである。どちらのSketch も殆どオリジナルのままである。しかし、表示部分に文字の重なりなどの問題があったので、フォントの大きさを変えたり、表示位置を変更し使わせて戴いている。
(注記:Sketch の中で init pin と言っているのは Trig Pin の事である。 まずArduinoを起動してから、次に Processing を起動してやれば良い。Processing が起動したとき、Arduino側の動きが一瞬停まるが、直ぐに同期して、PCの画面上に画像を映し出してくれる。)

《 Arduino Sketch 》

#include 
/*
code for arduino bord ultrasonic radar system.
*/            
Servo leftRightServo;         // set a variable to map the servo
int leftRightPos = 0;         // set a variable to store the servo position
const int numReadings = 10;   // set a variable for the number of readings to take
int index = 0;                // the index of the current reading
int total = 0;                // the total of all readings
int average = 0;              // the average
int echoPin = 6;              // the HC-SR04's echo pin
int initPin = 7;              // the HC-SR04's Trig pin
unsigned long pulseTime = 0;  // variable for reading the pulse
unsigned long distance = 0;   // variable for storing distance
/* setup the pins, servo and serial port */
void setup() {
  leftRightServo.attach(9);
  // make the init pin an output:
  pinMode(initPin, OUTPUT);
  // make the echo pin an input:
  pinMode(echoPin, INPUT);
  // initialize the serial port:
  Serial.begin(9600);
}  
/* begin rotating the servo and getting sensor values */
void loop() {
  for(leftRightPos = 0; leftRightPos < 180; leftRightPos++) {  // going left to right.
    leftRightServo.write(leftRightPos);
      for (index = 0; index <=numReadings;index++) {  // take x number of readings from the sensor and average them
        digitalWrite(initPin, LOW);
        delayMicroseconds(50);
        digitalWrite(initPin, HIGH);          // send signal
        delayMicroseconds(50);                // wait 50 microseconds for it to return
        digitalWrite(initPin, LOW);           // close signal
        pulseTime = pulseIn(echoPin, HIGH);   // calculate time for signal to return
        distance = pulseTime/58;              // convert to centimetres
        total = total + distance;             // update total

        delay(10);
      }
    average = total/numReadings;    // create average reading
 
    if (index >= numReadings)  {    // reset the counts when at the last item of the array
      index = 0;
      total = 0;
    }
    Serial.print("X");              // print leading X to mark the following value as degrees
    Serial.print(leftRightPos);     // current servo position
    Serial.print("V");              // preceeding character to separate values
    Serial.println(average);        // average of sensor readings
  }
  /*
  start going right to left after we got to 180 degrees
  same code as above
  */
  for(leftRightPos = 180; leftRightPos > 0; leftRightPos--) {  // going right to left
    leftRightServo.write(leftRightPos);
    for (index = 0; index <=numReadings;index++) {
      digitalWrite(initPin, LOW);
      delayMicroseconds(50);
      digitalWrite(initPin, HIGH);
      delayMicroseconds(50);
      digitalWrite(initPin, LOW);
      pulseTime = pulseIn(echoPin, HIGH);
      distance = pulseTime/58;
      total = total + distance;
      delay(10);
    }
    average = total/numReadings;
    if (index >= numReadings)  {
      index = 0;
      total = 0;
    }
    Serial.print("X");
    Serial.print(leftRightPos);
    Serial.print("V");
    Serial.println(average);
   }
}

《 Processing 用のSketch》

/*
Radar Screen Visualisation for HC-SR04
 Maps out an area of what the HC-SR04 sees from a top down view.
 Takes and displays 2 readings, one left to right and one right to left.
 Displays an average of the 2 readings
 Displays motion alert if there is a large difference between the 2 values.
 */
import processing.serial.*;   // import serial library
Serial arduinoport;           // declare a serial port
float x, y;              // variable to store x and y co-ordinates for vertices   
int radius = 350;        // set the radius of objects
int w = 300;             // set an arbitary width value
int degree = 0;          // servo position in degrees
int value = 0;           // value from sensor
int motion = 0;          // value to store which way the servo is panning
int[] newValue = new int[181];  // create an array to store each new sensor value for each servo position
int[] oldValue = new int[181];  // create an array to store the previous values.
PFont myFont;                   // setup fonts in Processing
int radarDist = 0;       // set value to configure Radar distance labels
int firstRun = 0;        // value to ignore triggering motion on the first 2 servo sweeps

/* create background and serial buffer */
void setup() {
  // setup the background size, colour and font.
  size(750, 450);
  background (0); // 0 = black
  myFont = createFont("verdana", 12);
  textFont(myFont);
  // setup the serial port and buffer
  arduinoport = new Serial(this, Serial.list()[0], 9600);
}

/* draw the screen */
void draw() {
  fill(0);                     // set the following shapes to be black
  noStroke();                  // set the following shapes to have no outline
  ellipse(radius, radius, 750, 750);  // draw a circle with a width/ height = 750 with its center position (x and y) set by the radius
  rectMode(CENTER);            // set the following rectangle to be drawn around its center
  rect(350, 402, 800, 100);    // draw rectangle (x, y, width, height)
  if (degree >= 179) {         // if at the far right then set motion = 1/ true we're about to go right to left
    motion = 1;                // this changes the animation to run right to left
  }
  if (degree <= 1) {           // if servo at 0 degrees then we're about to go left to right
    motion = 0;                // this sets the animation to run left to right
  }
  /* setup the radar sweep */
  /* 
   We use trigonmetry to create points around a circle.
   So the radius plus the cosine of the servo position converted to radians
   Since radians 0 start at 90 degrees we add 180 to make it start from the left
   Adding +1 (i) each time through the loops to move 1 degree matching the one degree of servo movement
   cos is for the x left to right value and sin calculates the y value
   since its a circle we plot our lines and vertices around the start point for everything will always be the center.
   */
  strokeWeight(7);             // set the thickness of the lines
  if (motion == 0) {           // if going left to right
    for (int i = 0; i <= 20; i++) {  // draw 20 lines with fading colour each 1 degree further round than the last
      stroke(0, (10*i), 0);    // set the stroke colour (Red, Green, Blue) base it on the the value of i
      line(radius, radius, radius + cos(radians(degree+(180+i)))*w, radius + sin(radians(degree+(180+i)))*w); // line(start x, start y, end x, end y)
    }
  } else {                     // if going right to left
    for (int i = 20; i >= 0; i--) {  // draw 20 lines with fading colour
      stroke(0, 200-(10*i), 0);      // using standard RGB values, each between 0 and 255
      line(radius, radius, radius + cos(radians(degree+(180+i)))*w, radius + sin(radians(degree+(180+i)))*w);
    }
  }
  /* Setup the shapes made from the sensor values */
  noStroke();                  // no outline
  /* first sweep */
  fill(0, 50, 250);            // set the fill colour of the shape (Red, Green, Blue)
  beginShape();                // start drawing shape
  for (int i = 0; i < 180; i++) {    // for each degree in the array
    x = radius + cos(radians((180+i)))*((oldValue[i])); // create x coordinate
    y = radius + sin(radians((180+i)))*((oldValue[i])); // create y coordinate
    vertex(x, y);              // plot vertices
  }
  endShape();                  // end shape
  /* second sweep */
  fill(0, 110, 0);
  beginShape();
  for (int i = 0; i < 180; i++) {
    x = radius + cos(radians((180+i)))*(newValue[i]);
    y = radius + sin(radians((180+i)))*(newValue[i]);
    vertex(x, y);
  }
  endShape();
  /* average */
  fill(0, 170, 0);
  beginShape();
  for (int i = 0; i < 180; i++) {
    x = radius + cos(radians((180+i)))*((newValue[i]+oldValue[i])/2); // create average
    y = radius + sin(radians((180+i)))*((newValue[i]+oldValue[i])/2);
    vertex(x, y);
  }
  endShape();
  /* if after first 2 sweeps, highlight motion with red circle*/
  if (firstRun >= 360) {
    stroke(150, 0, 0);
    strokeWeight(1);
    noFill();
    for (int i = 0; i < 180; i++) {
      if (oldValue[i] - newValue[i] > 35 || newValue[i] - oldValue[i] > 35) {
        x = radius + cos(radians((180+i)))*(newValue[i]);
        y = radius + sin(radians((180+i)))*(newValue[i]);
        ellipse(x, y, 10, 10);
      }
    }
  }
/* set the radar distance rings and out put their values, 50, 100, 150 etc.. */
  for (int i = 0; i <=6; i++) {
    noFill();
    strokeWeight(1);
    stroke(0, 255-(30*i), 0);
    ellipse(radius, radius, (100*i), (100*i)); 
    fill(200, 200, 200);
    noStroke();
    text(Integer.toString(radarDist+50), 380, (305-radarDist), 50, 50);
    radarDist+=50;
  }
  radarDist = 0;
/* draw the grid lines on the radar every 30 degrees and write their values 180, 210, 240 etc.. */
  for (int i = 0; i <= 6; i++) {
    strokeWeight(1);
    stroke(0, 55, 0);
    line(radius, radius, radius + cos(radians(180+(30*i)))*w, radius + sin(radians(180+(30*i)))*w);
    fill(200, 200, 200);
    noStroke();
    if (180+(30*i) >= 300) {
      text(Integer.toString(180+(30*i)), (radius+10) + cos(radians(180+(30*i)))*(w+10), (radius+10) + sin(radians(180+(30*i)))*(w+10), 25, 50);
    } else {
      text(Integer.toString(180+(30*i)), radius + cos(radians(180+(30*i)))*w, radius + sin(radians(180+(30*i)))*w, 60, 40);
    }
  }
/* Write information text and values. */
  noStroke();
  fill(0);
  rect(350, 402, 800, 100);
  fill(0, 100, 0);
  text("Degrees: "+Integer.toString(degree), 100, 380, 100, 50);  // use Integet.toString to convert numeric to string as text() only outputs strings
  text("Distance: "+Integer.toString(value), 100, 400, 100, 50);  // text(string, x, y, width, height)
  text("Radar Plot Graph ", 540, 380, 250, 50);
  fill(0, 50, 250);
  textSize(22);
  text("SP ROBOTICS : RADAR ", 570, 480, 650, 200);
  textSize(12);
  fill(0);
  rect(70, 60, 150, 100);
  fill(255, 255, 0); 
  text("Screen Key:", 100, 50, 150, 50);
  fill(0, 50, 250);
  rect(30, 53, 10, 10);
  text("First sweep", 115, 70, 150, 50);
  fill(0, 110, 0);
  rect(30, 73, 10, 10);
  text("Second sweep", 115, 90, 150, 50);
  fill(0, 170, 0);
  rect(30, 93, 10, 10);
  text("Average", 115, 110, 150, 50);
  noFill();
  stroke(150, 0, 0);
  strokeWeight(1);
  ellipse(29, 113, 10, 10); 
  fill(150, 0, 0);
  text("Motion", 115, 130, 150, 50);
}

/* get values from serial port */
void serialEvent (Serial arduinoport) {
  String xString = arduinoport.readStringUntil('\n');  // read the serial port until a new line
  if (xString != null) {  // if theres data in between the new lines
    xString = trim(xString); // get rid of any whitespace just in case
    String getX = xString.substring(1, xString.indexOf("V")); // get the value of the servo position
    String getV = xString.substring(xString.indexOf("V")+1, xString.length()); // get the value of the sensor reading
    degree = Integer.parseInt(getX); // set the values to variables
    value = Integer.parseInt(getV);
    oldValue[degree] = newValue[degree]; // store the values in the arrays.
    newValue[degree] = value;  
 /* sets a counter to allow for the first 2 sweeps of the servo */
    firstRun++;
    if (firstRun > 360) {
      firstRun = 360; // keep the value at 360
    }
  }
}

07/14/2017

Arduinoで(太陽)光の方向を追いかける2軸サーボ(New Algorithm)

Img_20170708_153936 Img_20170708_153958 Img_20170706_101405 Img_20170706_101420













Arduino Nanoで(太陽)光の方向を追いかける装置をブレッドボード上に作ってうまく稼働したので、小さなユニバーサル基板(60mm x 40mm)の上に部品を置いて製作してみた。
ケースも100均ショップで、適当な大きさの円筒形貯金箱を入手したので、その中に基板を入れ、ケース上面にLDRセンサー(前回作ったもの)を装着したサーボを固定した。

装置が新しくなったので、それに併せて光を追いかけるスケッチも自分のオリジナルな発想でロジックを考え、光の方向を探索する時間も短縮できるようにした。その結果、光を追いかける速度は格段に速くなり、室内などでテストした際、室内灯を消灯すると、初期の位置に戻り、点灯すると直ちに灯りの位置を向く。またセンサー部に手を翳すと、手の影を嫌ってより明るい方向を向くように動くので、その仕草がなかなか可愛い。

と書いてから、本当に明るい方向を向いているのだろうか・・・と初期の動きが気になったので、センサーを経度方向約170度分を周して、そこで明るい場所を探索の初期値にするようにスケッチを追加した。
緯度方向は、初期値を決め打ちして、探査をしていないので、そのために初期値の緯度・経度値から位置を確定するまでに少し時間がかかる場合がある。

スケッチは当初のものより少し長くなってしまった。しかし、まあまあ納得がいくようになったのでスケッチを公開することにする。
緯度方向の初期値探査が欲しい場合には、私のスケッチに倣って探せば良いし、経度方向の初期探査が不要なら、(34~55行までと関連のある部分を)外せばいい。それでも大きな問題無く稼働すると思う。

《Sketch》

// The Sunlight Tracker New Algorithm  by Savvy 2017.07.14
//
#include <Servo.h>
Servo servohori;
int svLimitMax = 175;
int svLimitMin =   5;
int servoh     = svLimitMin;   // Initial value of search angle

Servo servoverti; 
int svLimitHigh = 120;
int svLimitLow  =   5;
int servov      =  45;  // Initial value of search angle

// Assign the wireing color to LDRs
int ldrtopl = 2;  // top left LDR Green wiring
int ldrtopr = 1;  // top right LDR Yellow wiring
int ldrbotl = 3;  // bottom left LDR Blue wiring
int ldrbotr = 0;  // bottom right LDR Red wiring

int topl;
int topr;
int botl;
int botr;

int hstep;
int vstep;
int alpha;
int  beta;

 void setup () {
  servohori.attach(10);    // horizontal motor servo  (Blue wiring)
  servoverti.attach(9);    // vertical motor servo  (Green wiring)

// search initial horizontal angle between servo limit min-max angle
  int i    = 0;
  int imin = 0;
  int lx[]  = { 9999, 9999, 9999, 9999, 9999, 9999, 9999};
  int hstp  = (svLimitMax - svLimitMin) / 6;
  int lxmin = 9999;
  alpha = svLimitMin;
  beta  = servov;

  for ( i=0; i<7; i++ )
  {
   lx[i] =  wlight( alpha, beta);
   alpha = alpha + hstp;
   if( lx[i] < lxmin )
   { lxmin = lx[i];
    imin = i;
     }
  }
  delay(200);
  // this is the initial location of horizontal servo
  servohori.write( svLimitMin + hstp*imin );
}
void loop(){
  // Read the servo set angle at the moment
  servoh = servohori.read();
  servov = servoverti.read();
  
  // Read the light number of each LDR
  topl = analogRead(ldrtopl);
  topr = analogRead(ldrtopr);
  botl = analogRead(ldrbotl);
  botr = analogRead(ldrbotr);
  
  // calculate the average value
  int avgtop   = (topl + topr) / 2;   // average of top LDRs
  int avgbot   = (botl + botr) / 2;   // average of bottom LDRs
  int avgleft  = (topl + botl) / 2;   // average of left LDRs
  int avgright = (topr + botr) / 2;   // average of right LDRs

  if( (avgtop < 1012) & (avgbot < 1012) )  // the servo acts while in the light
  {
    hstep = xstep(avgright,avgleft);  // determination of the horizontal serch step angle
    servoh = servoh + hstep;
    
    if((svLimitMin < servoh) & (servoh < svLimitMax))  // acts between max - min limits
    { 
       servohori.write(servoh);
    }
    
    vstep = xstep(avgtop,avgbot);   // determination of the vertical serch step angle 
    servov = servov + vstep;
    
    if((svLimitLow < servov) & (servov < svLimitHigh))
    {
        servoverti.write(servov);
    }       
  }
  delay(200);
}
int xstep(int avg1, int avg2)
  // classify the step value from the two light value
{
  int jcase = avg1 - avg2;
  int result;
    
    if(jcase < -30 ) {
      result = 10; 
    }
    if((-30 <= jcase) & (jcase < -10)){
      result = 3;
    }
    if((-10 <= jcase) & (jcase <  -2)){
      result = 1;
    }
    if(( -2 <= jcase) & (jcase <  2)){
      result = 0;
    }
    if(( 2 <= jcase) & (jcase < 10)){
      result = -1;
    }
    if(( 10 <= jcase) & (jcase < 30)){
      result = -3;
    }
    if( 30 <= jcase ){
      result = -10;
    }
    return result;
}
int wlight(int alpha, int beta)
{
   servohori.write(alpha);
   servoverti.write(beta);
   int loc  = (analogRead(ldrtopl) + analogRead(ldrtopr) + analogRead(ldrbotl) + analogRead(ldrbotr)) / 4; 
   delay(200);
   return loc;
}

07/04/2017

Arduinoで作る太陽光の方向を向く2軸サーボ

Img_20170704_203131 Img_20170629_224615 Arduino Nano (ATmega328)を使って、(太陽)光のある方向を向く2軸サーボ装置を作ってみた。
・Arduinoは、 Nano を
・ServoMotorは、 SG92R を 2個使い
・フォトレジスターは、 手元にあったものを 4個使った。はじめは、抵抗値の基本値を選ぶ必要があるのか・・・と思ったが、ロットが揃っているものなら、それを使って良さそうだ。

Webで見つけたサイト「Arduino Solar Tracker(http://www.electronicshub.org/arduino-solar-tracker/
」などを参考に製作した。

Img_20170704_090341 Img_20170704_090529 Img_20170704_090634 フォトレジスタと抵抗の固定は、小型のユニバーサル基板の上に半田付けし、光の方向を捉え易くするために、写真のように十字型の覆いを設けた。この部分の回路図は、参考にさせてもらったブログの通りであると思う。

基板の後ろから見て、右下(赤色の配線⇒A0)、右上(黄色の配線⇒A1)、左上(緑色の配線⇒A2)、左下(青色の配線⇒A3)とした。

サーボモーターは、水平方向の制御線を水色⇒D10へ、垂直方向の制御線を白⇒D9へと繋いだ。

Solar_trackerflow_chart_20170704 最初、スケッチは参考にしたブログのものをそのまま使ったが、収束に時間がかかる事と、収束した状態でもサーボを動かしているので、左図のようにフローチャートを作成して、収束時間を短縮し、収束したらサーボモータを停めるようにスケッチを変更した。細かな所は、左図を確認すれば解るだろうと思う。


スケッチは、制御の状態を確認したかったので、オリジナルのスケッチに対してシリアルモニターに途中の状態を出力するように追記した。

出力結果を確認し、光源の位置から離れていると思われる場合には、光源に向かうサーボモータ回転角の刻み幅を大きくし、光源に近づいてくると、刻み幅を小さくするようにした。


閾値は明るさの差 eps を”5”としたが、動き方を見て調整するのが良いだろう。

稼働させてみると、光のある方向への収束時間は随分早くなったように思う。

スケッチは、下記のようである。

// The Sunlight Tracker 2017.07.04
//
#include <Servo.h>
Servo servohori;
int servoh = 90;
int servohLimitHigh = 170;
int servohLimitLow = 10;
int hstep = 1;
int eps = 5;

Servo servoverti; 
int servov = 30; 
int servovLimitHigh = 120;
int servovLimitLow = 10;
int vstep = 1;

//Assigning LDRs
int ldrtopl = 2; //top left LDR Green wiring
int ldrtopr = 1; //top right LDR Yellow wiring
int ldrbotl = 3; // bottom left LDR Blue wiring
int ldrbotr = 0; // bottom right LDR Red wiring

 void setup () {
  Serial.begin(9600);   // to check the sketch, print out to the monitor
  servohori.attach(10); // horizontal motor servo  (Blue wiring)
  servohori.write(servoh);
  servoverti.attach(9); // vertical motor servo  (Green wiring)
  servoverti.write(servov);
  delay(500);
 }

void loop(){
  servoh = servohori.read();
  servov = servoverti.read();
  Serial.print("Location of Servo   Horizontal:");
  Serial.print(servoh);
  Serial.print("deg,  Vertical:");
  Serial.print(servov);
  Serial.println("deg");
  
  //capturing analog values of each LDR
  int topl = analogRead(ldrtopl);
  int topr = analogRead(ldrtopr);
  int botl = analogRead(ldrbotl);
  int botr = analogRead(ldrbotr);
  Serial.println(" ");
  Serial.println("Captured LDR value ");
  Serial.print("TL:");
  Serial.print(topl);
  Serial.print("  TR:");
  Serial.println(topr);
  Serial.print("BL:");
  Serial.print(botl);
  Serial.print("  BR:");
  Serial.println(botr);
  
  // calculating average
  int avgtop = (topl + topr) / 2;   //average of top LDRs
  int avgbot = (botl + botr) / 2;   //average of bottom LDRs
  int avgleft = (topl + botl) / 2;  //average of left LDRs
  int avgright = (topr + botr) / 2; //average of right LDRs

  if (avgtop < avgbot) {
    Serial.print("Average Value  ");
    Serial.print("Top:");Serial.print(avgtop);
    Serial.print(" < ");
    Serial.print("Bottom:");Serial.println(avgbot);
    
    vstep = abs(avgtop-avgbot);
    if( vstep < eps ){
      Serial.println("Top is lighter than Bottom =< turn Up");
      vstep = 1 + vstep/4;
      Serial.print("servov = ");Serial.print(servov);Serial.print("+");
      Serial.println(vstep);
      servoverti.write(servov + vstep); 
      }
    else {  // to stabilize the servo-motor
      Serial.print("|Top-Bottom|=<");Serial.print(eps);
      Serial.println(" then Stop the Vertical servo");     
    }
    if (servov > servovLimitHigh) { 
      servov = servovLimitHigh;
     }
    delay(10);
  }
  else if (avgbot < avgtop) {
      Serial.print("Average Value of LDR  ");
      Serial.print("Bottom:");Serial.print(avgbot);
      Serial.print(" < ");
      Serial.print("Top:");Serial.println(avgtop);

    vstep = abs(avgtop-avgbot); 
    if( vstep > eps ){   
      Serial.println("Bottom is lighter than Top => turn Down");
      vstep = 1 + vstep/4;
      Serial.print("servov = ");Serial.print(servov);Serial.print("-");
      Serial.println(vstep);     
      servoverti.write(servov - vstep);
    }
    else {  // to stabilize the servo-motor
      Serial.print("|Top-Bottom|=<");Serial.print(eps);
      Serial.println(" then Stop the Vertical servo");       
    }
    if (servov < servovLimitLow) {
    servov = servovLimitLow;
    }
    delay(10);
  }
  else {
  }
  
  if (avgleft > avgright) {
      Serial.print("Average Value of LDR  ");
      Serial.print("Left:");Serial.print(avgleft);
      Serial.print(" > ");
      Serial.print("Right:");Serial.println(avgright);

      hstep = abs(avgleft-avgright);
    if( hstep > eps ){  
      Serial.println("Right is lighter than Left => turn Right");
      hstep = 1 + hstep/4;
      Serial.print("servoh = ");Serial.print(servoh);Serial.print("+");
      Serial.println(hstep);
      servohori.write(servoh + hstep);
    }
    else {   // to stabilize the servo-motor
      Serial.print("|Left-Right|=<");Serial.print(eps);
      Serial.println(" then Stop the Horizontal servo");         
    }
    if (servoh > servohLimitHigh) {
    servoh = servohLimitHigh;
    }
    delay(10);
  }
  else if (avgright > avgleft) {
      Serial.print("Average Value of LDR  ");
      Serial.print("Right:");Serial.print(avgright);
      Serial.print(" > ");
      Serial.print("Left:");Serial.println(avgleft);    

    hstep = abs(avgleft-avgright);
    if( hstep > eps ){   
      Serial.println("Left is lighter than Right => turn Left");
      hstep = 1 + hstep/4;
      Serial.print("servoh = ");Serial.print(servoh);Serial.print("-");
      Serial.println(hstep);     
      servohori.write(servoh - hstep);
    }
    else {  // to stabilize the servo-motor
      Serial.print("|Left-Right|=<");Serial.print(eps);
      Serial.println(" then Stop the Horizontal servo");
    }
    if (servoh < servohLimitLow) {
      servoh = servohLimitLow;
     }
    delay(10);
  }
  else  {
  }
  delay(50);
}


《備考》
ここに、
 hstep : 水平方向回転サーボモータの回転刻み角度
  vstep : 垂直方向回転サーボモータの回転刻み角度
 esp : 左右、上下のフォトレジスタから読取った値を比較した際のサーボモータの停止判定値

問題は、周囲が暗くなると、灯りを探し回るので、この動きを止める必要がありそうだ。
多分、フォトレジスタで計測した際の明るさが、ある程度大きくなったら、サーボモータの動きを止めれば良いと思う。これは、次にソーラーパネルと組み合わせる計画があるので、その際に改造するつもりだ。

06/11/2017

Processing 3.3.4 のインストール

Arduino で Sonic Radar の画像処理を行うには、Processingが必要な事が分かったので、早速インストールしてみることにした。
他のアプリと同じようにやってみてもインストールされないし・・・

ネットで調べてみると、「Processing2のダウンロードとインストール」が見つかり、参考にしてWindows64bit版で試してみた。

ところが、解凍しても上手くいかない・・・僕が使った解凍ソフトは、お気に入りの
Lhaplus V1.73 (2017/06/11時点の最新版は 1.74)」だが、エラーが発生し、巧くゆかないようだ。それで「7-Zip」で解凍してみた。問題無く解凍出来る。

Cドライブの Program Files の中に、僕は「Processing」というフォルダを作成し、解凍したファイルを全て移動させた。
参考にさせて戴いたブログのProcessing v2 には「launch4j」というフォルダがあるが、僕が解凍したものでは、そのフォルダは存在していなかったので、解凍内容が少し違っている。

そして、「Processing.exe」のショートカットアイコンをデスクトップに貼り付けた。

Provessing3 デスクトップの『Processing ショートカットアイコン』をクリックすると、左図のように起動した!


05/27/2017

肩を温める USB電源(5V)を使った自作ヒーター

Img_20170527_071241 50肩になってしまった。就寝中に肩が冷えると痛みが増す。肩部を温めるための何かいい方法は無いものかと探していたが、そのようなものは、意外に見つからない。別の電子工作のアイデアを探していた時、カメラレンズや鏡筒のための「結露対策ヒーターの作り方」というブログに出会った。
USB(5V)から電源をとり、1mほどの長さのニクロム線に500mAの電流を流してレンズなどを温めるというアイデアになっている。この方法を応用できないか・・・とやってみた。


僕は、Amazonから これに必要な部品①,②を購入
①単位長さ当たりの抵抗値が、参考にしたブログに記載のものと同等レベルのニクロム線 (557円、送料込み)
  ワイヤ径0.4mm、  抵抗値 8.674Ω/m、 AWG26、 15m巻き
  使うのは1mほどの長さなのだが、この単位でしか購入できなかった。

注記)抵抗線は、線径が細くなるほど単位長さ当たりの抵抗値が高くなる。
 用途に応じて線径を決めるのが良い。
  (例) 矢印(⇒)の右側は、10Ωになる抵抗線(ニクロム線)の概略長さ
    0.7mm - 2.93Ω/m ⇒ 341cm
    0.6mm - 4.103Ω/m ⇒ 244cm
    0.5mm - 5.755Ω/m ⇒ 173cm 
    0.4mm - 8.674Ω/m ⇒ 115cm
    0.3mm - 15.42Ω/m ⇒ 65cm
    0.25mm - 22.21Ω/m ⇒ 45cm

②耐熱ガラスチューブ内径1.5mm (@156円+配送料560円)
  1mの長さがこの値段。使うのは1.2mほどになるので、1mでは短い・・・
  不足する長さの分については、僕は細目の熱収縮チューブを使った。
  実は、細い熱収縮チューブも購入し、ニクロム線をその中に入れようとしたが、50cmほど挿入したところから、挿入する抵抗が高くなり、1mのニクロム線を貫通させることは無理そうなので、耐熱ガラスチューブを使うことにした。この問題は透水性がありそうなので、電気の絶縁性能がどうかな・・・と不安である。

 その他、アッセンブリに必要な部品として、熱収縮チューブ、圧着スリーブ、自己融着テープ、USBケーブル、モバイルバッテリー等があるが、身の回りにあるものから、かき集めた。

参考にしたブログにも注意事項が細かく書かれていたが、
私がここに書いているのは私のメモであって、これと同じようなものを作ろうとして、いかなる事故が起きたとしても、私は一切の責任を負いません。自作するに際しての責任は、全てあなたの自己責任になります。

《作り方》

Img_20170526_115611 Img_20170526_120530結露対策ヒーターの作り方」を参考に、5V、500mAでほのかに温かいという仕様にすべく、抵抗線(ニクロム線)の値も市販品から 8.674Ω/m のものを選んで、入手後プラスチックスケールの上に抵抗線を貼り付け、20cm当たりの抵抗値を実測してみた。
抵抗値は 1.7Ωであった。

そのため、抵抗値が10Ωになるように、抵抗線の長さをほぼ 118cmと決めた。

Img_20170526_125638 Img_20170526_132144 Img_20170526_124651 Img_20170526_132550 ニクロム線は、絶縁のない裸状態なので、ショートさせたりする危険性を避けるために、
耐熱ガラスチューブを被せ、ガラスチューブ長さが不足する両端部を細目の熱収縮チューブを被せて、写真のようにニクロム線を被覆した。
バッテリーに繋いでみたが、この状態ではずっと手で握って居られるほど低い温度である。
多分、
耐熱ガラスチューブの効果が高すぎるのかもしれない。しかし、ガラスチューブ表面の温度は時間が経過すれば、内部も外部も同じ状態(平衡状態)になるはずである。

耐熱ガラスチューブを使わず、熱収縮チューブを使って絶縁するという(参考にしたブログと同じ)方法もあるが、細い熱収縮チューブを使うと、0.4mmの径のニクロム線を1mの長さ通すのは至難の業が必要になる。ガラスチューブは接触抵抗が低いので、難なく通すことが出来る。

肩用のサポータにこのニクロム線を縫い付けるのだが、ニクロム線の長さはある程度必要なため、温度を上げるためにニクロム線長さを短縮したくはないので、当初計画通り10Ωを保持する長さのままとした。

Img_20170526_133605 Img_20170526_161449 Img_20170526_161513 USBケーブルの端を開き、圧着スリーブを用いてニクロム線と接続した。この上にも太めの熱収縮チューブを被せ、その後、自己融着テープを用いて絶縁保護とその部分の補強を行った。


《使用の状況》

温かさ : 組込む前に、もう少し細かく詰めれば良かった。温度は低めで、もう少し上げられれば・・・と思う。

ニクロム線の配置間隔をもう少し詰めた方が良さそうに思う。そのためには、ニクロム線の単位長さ当たりの抵抗値をもう少し低いものを使って、扱える線の長さを延ばし配置すればと考えられる。

例えば前述の「線径-単位長さ当たりの抵抗値」を参考に、0.6mmの線径の抵抗線を使えば、10Ωとするには244cmとれる為、今回使った線径0.4mmの線配置の空いた場所に線を配置できる。そのため、面当たりに近づき、より「ほんわか」した温かさを実現できそうだ。

05/14/2017

Arduino用mp3シールドを使い TVリモコンで指定したMP3ファイル再生

Mp3tf16p_controlled_by_tv_ir_remote Img_20170514_112430 MP3-TF-16Pを使って、MP3形式で保存したファイルを、TVリモコンを使って再生する方法。
ブレッドボードを使った回路図は左側、実際に組んだ回路は右側のようになる。



Mp3tf16p_controlled_by_tv_ir_remo_2左は、Fritzingで記載した回路図。


MP3-TF-16Pで使うマイクロSDカードに保存するファイルは、ファイルを保存した順番とファイル(番号)名を合わせておく必要がある。すなわち「0001.mp3」「0002.mp3」・・・「0013.mp3」のように、ファイル(トラック)番号を書かなければいけない。

sketchでは、赤外線受信で受け取ったREGZA TVリモコンの信号を、switch-case制御文を使って条件分岐させて、各条件(ここではTVチャンネル番号)に合わせたmp3音源ファイルを1つだけ再生するようにしている。
(注記)僕が使っている東芝のREGZA TV の赤外線コントローラー信号は、ここで受信したものを使っている。

TVリモコンの10チャンネルを赤外線受信で受け取ると、16進数で「0A」、11チャンネルは「0B」、12チャンネルは「0C」となるので、case分ではその16進数を使う必要があるので、間違えないように・・・。

mp3の音源ファイルを1つ、例えば3番目のファイル(0003.mp3)を演奏するには、case文 0x03 のところに、
  myDFPlayer.play(3); と書けばよい。

また、11番目のファイル(0011.mp3)を演奏する場合には、case文 0x0B のところに、
  
myDFPlayer.play(11); と書けばよい。


《sketch》

/*************************************************************************************** 
MP3-TF-16P(DFPlayer) - A Mini MP3 Player for Arduino controlled by TV-remote
****************************************************************************************/
#include "Arduino.h"
#include <SoftwareSerial.h>
#include "DFRobotDFPlayerMini.h"
#define IR_PIN      2             // 赤外線受信モジュール接続ピン番号
#define DATA_POINT  3             // 受信したデータから読取る内容のデータ位置 東芝REGZA TV
SoftwareSerial mySerial(10,11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
#include <Wire.h>                 // ライブラリ内部で使用するので定義必要

void setup()
{
    Serial.begin(115200) ;       // パソコン(ArduinoIDE)とシリアル通信の準備を行う
    mySerial.begin(9600);
    pinMode(IR_PIN,INPUT) ;       // 赤外線受信モジュールに接続ピンをデジタル入力に設定

    if (!myDFPlayer.begin(mySerial)) {  // Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  Serial.println(F("MP3-TF-16P(DFPlayer Mini) online."));
  myDFPlayer.volume(25);               //Set volume value. From 0 to 30
  Serial.println("起動しました") ;
}
void loop()
{
    int ans ;
    ans = IRrecive() ;                 // 赤外線リモコンのデータを受信する
        if (ans != 0) {
            Serial.println(ans,HEX) ; // REGZA TV リモコンからデータを受信し番号に準じて発音

            switch(ans) {
            case 0x01: 
                Serial.println("覚悟を決めましょう") ;
                myDFPlayer.play(1);
                break ;
                
            case 0x02:
                Serial.println("神の裁きを") ;
                myDFPlayer.play(2);
                break ;
                
            case 0x03:
                Serial.println("ベートーベンの運命") ;
                myDFPlayer.play(3);
                break ;
                      
            case 0x04:
                Serial.println("運命") ;
                myDFPlayer.play(4);
                break ;
                
            case 0x05:
                Serial.println("スキ有り") ;
                myDFPlayer.play(5);
                break ;
                
            case 0x06:
                Serial.println("悔い改めよ") ;
                myDFPlayer.play(6);
                break ;
                
            case 0x07:
                Serial.println("こいつは厄介そうじゃな") ;
                myDFPlayer.play(7);
                break ;
                
            case 0x08:
                Serial.println("ついに迎えが") ;
                myDFPlayer.play(8);
                break ;
                
            case 0x09:
                Serial.println("大勢で拍手") ;
                myDFPlayer.play(9);
                break ;
                
            case 0x0A:
                Serial.println("学校のチャイム") ;
                myDFPlayer.play(10);
                break ;
                                
            case 0x0B:
                Serial.println("うっそお!") ;
                myDFPlayer.play(11);
                break ;
                                
            case 0x0C:
                Serial.println("ぱんぱかぱ~ん") ;
                myDFPlayer.play(12);
                break ;  
           }
     }
}
// 赤外線リモコンのデータを受信する処理関数
int IRrecive()
{
     unsigned long t ;
     int i , j ;
     int cnt , ans ;
     char IRbit[64] ;

     ans = 0 ;
     t = 0 ;
     if (digitalRead(IR_PIN) == LOW) {
          // リーダ部のチェックを行う
          t = micros() ;                               // 現在の時刻(us)を得る
          while (digitalRead(IR_PIN) == LOW) ;         // HIGH(ON)になるまで待つ
          t = micros() - t ;                           // LOW(OFF)の部分をはかる
     }
     // リーダ部有りなら処理する(3.4ms以上のLOWにて判断する)
     if (t >= 3400) {
          i = 0 ;
          while(digitalRead(IR_PIN) == HIGH) ;          // ここまでがリーダ部(ON部分)読み飛ばす
          // データ部の読み込み
          while (1) {
               while(digitalRead(IR_PIN) == LOW) ;      // OFF部分は読み飛ばす
               t = micros() ;
               cnt = 0 ;
               while(digitalRead(IR_PIN) == HIGH) {     // LOW(OFF)になるまで待つ
                    delayMicroseconds(10) ;
                    cnt++ ;
                    if (cnt >= 1200) break ;            // 12ms以上HIGHのままなら中断
               }
               t = micros() - t ;
               if (t >= 10000) break ;                  // ストップデータ
               if (t >= 1000)  IRbit[i] = (char)0x31 ;  // ON部分が長い
               else            IRbit[i] = (char)0x30 ;  // ON部分が短い
               i++ ;
          }
          // データ有りなら指定位置のデータを取り出す
          if (i != 0) {
               i = (DATA_POINT-1) * 8 ;
               for (j=0 ; j < 8 ; j++) {
                    if (IRbit[i+j] == 0x31) bitSet(ans,j) ;
               }
          }
     }
     return( ans ) ;
}


《参考資料》

①MP3-TF-16Pを使ったArduinoとの結線・mp3ファイルの再生方法
 「Electronic Circuit Projects」の中の「
Mp3 Player Using Arduino and DFPlayer 」より
 ここにあるDFPlayerは、私が使ったMP3-TF-16Pと基本的に同じもののようだ。
 僕は、RX,TXの接続部分を参考にさせてもらった。

②IR TVリモコンの解析
 赤外線通信の実験パート2
 (赤外線リモコンを送信器にして何か動かす)
 ここに書かれている赤外線受信のサンプル回路はシンプルなので、実験もやり易い。
 赤外線受信解析のスケッチも、ここのスケッチを使わせてもらった。


③コントロール方法
 DFPlayer Mini SKU:DFR0299
 ここでは「DFPlayerMini」を使ってArduino用のMP3プレーヤーのコントロール方法について記載されているが、私が使った「MP3-TF-16P」もほぼ同じなので、スケッチなどは大変参考になった。一部のコードを、私のスケッチへコピーして使わせてもらっている。

④MP3音声ファイル(著作権フリー素材)

 効果音ラボ よりdownloadし、使わせて戴きました。

 

05/08/2017

TWE-Lite 半完成品セミキットで通信が出来ないと思ったら・・・

TWE-Liteを使ったおもちゃを作ろうと思い、「TEW-Lite DIP-PCB (半完成品セミキット)」を2個追加購入した。半完成品なので自分でコネクタを半田付けしなくてはいけないが、価格が300円ほど安価であるし、自分の半田付け技術は悪くないと思っているので、安価なセミキットを購入した。
部品到着後、早速半田付けして、ブレッドボード上で回路を組み立て試験してみたが、回路が動かない・・・。
それほど難しい回路構成でもないので、何が悪いのだろうと悩んでしまった。

Img_20170508_093812 Img_20170508_093826 Img_20170508_093844 二日後、Lチカ回路での問題が無いかを確認すると・・・、
①DI1、DI2はプッシュボタンを押していないのに、電源を入れた段階で既に子機側のLEDが点灯している!

②DI3、DI4はプッシュボタンに連動してLEDが点灯する。

プッシュボタンの設置には問題が無い・・・回路図とブレッドボード上の配線を何度も見直してみるが、間違いはない。
TWE-Liteを疑ってみた。電源投入後に親機側の押しボタンがONになっている事から、テスターでDI1、DI2、DI3、DI4 とGND間の抵抗をあたってみた。
DI1、DI2は、導通している!
DI3、DI4
は、高い抵抗値を示している。

Img_20170508_094043 ブレッドボード上で差し替えして、多分問題が無さそうだったTWE-LiteについてDI1、DI2、DI3、DI4とGND間の抵抗値を図ってみると、DI1、DI2、DI3、DI4全てが高い抵抗値を示している。
今回2個のTWE-Liteを購入したが、1個は壊れている! 半完成品なので、納品された当初からの問題なのか、私が半田付けしブレッドボード上で回路を組立していた時に壊してしまったのかは定かではない・・・。
今回TWE-Liteを使って造ろうと思っていた「おもちゃ」へのモチベーションが一気に下がってしまった・・・。

次にTWE-Liteを購入する際には、完成品を買った方が良さそうだ。

05/03/2017

TVリモコンを使ってArduinoに喋らせる

Img_20170503_154904 Img_20170503_083736 家電用リモコンを利用して、Arduinoから音を発する装置を考えた。(左写真:試作完成品)
家電のリモコンは容易に入手できるし、コントローラを新たに製作する時間を短縮することが出来る。ただし、TVリモコンは赤外線式なので、見通し出来る場所でしか使えないが、5mぐらいの距離なら問題なく使う事が出来るだろう。


.
Img_20170503_210742 先ず、コントロールするには、赤外線リモコンの制御信号を入手する必要がある。それで、下記のブログを参考にして、リモコンの制御信号を確認した(上写真右側)。この装置は、下表の状況が分かれば要らなくなるが、各種リモコンのコードを解析するのには簡単に作成でき良いものだなぁと思う。

僕の手元にある東芝REGZAのデジタルTVリモコン(CT-90451)のコードは、次のようになっていた。


.

東芝REGZA デジタルTVリモコンコード

  受信コード                         ボタン名称
00000010111111011000000001111111 ( 40 BF 1 FE )   1
00000010111111010100000010111111 ( 40 BF 2 FD )   2
00000010111111011100000000111111 ( 40 BF 3 FC )   3
00000010111111010010000011011111 ( 40 BF 4 FB )   4
00000010111111011010000001011111 ( 40 BF 5 FA )   5
00000010111111010110000010011111 ( 40 BF 6 F9 )   6
00000010111111011110000000011111 ( 40 BF 7 F8 )   7
00000010111111010001000011101111 ( 40 BF 8 F7 )   8
00000010111111011001000001101111 ( 40 BF 9 F6 )   9
00000010111111010101000010101111 ( 40 BF A F5 )   10
00000010111111011101000000101111 ( 40 BF B F4 )   11
00000010111111010011000011001111 ( 40 BF C F3 )   12
00000010111111010100100010110111 ( 40 BF 12 ED ) 電源

したがって、受信したデータから読取る内容のデータ位置は 「3」 となる。


《Fritzingで描いた回路》

Tv 図中左が Arduino UNO で、
右側は、ブレッドボード上 中央部に配置した ATP3012F6-PU と関連する周辺部品である。

僕が製作した実物は、Arduinoユニバーサル プロトシールド基板の上に小型のブレッドボードを貼り付けたものに
ATP3012F6-PU を挿して配線したが、赤外線受光部はブレッドボード基板に載せきれなかったので、基板の上に半田付けしてある。

発生する音声は、上図にあるようなスピーカー直付けでは聞こえないほど音が小さいので、アクティブスピーカーを繋いで聞くのが良い。


《使い方》
TVリモコンをシステムの受光部に向けて、「1」~「6」及び「電源」のキーを押すと、Arduinoが、スケッチに書いた言葉を「女性の声で・・・」発する。
1:ただいま解析中
2:脈拍が上昇しています
3:ゆっくりしていってね
4:よろしいですか?

5:チャイム音J(ポンという音)
6:チャイム音K(ピンという音)
電源:連続チャイム音(JとKの組み合わせ音)

スケッチプログラムの中で、ローマ字で書いてある部分を
ATP3012F6-PUが読んでくれる。
番号に対応するところを、この使用者が変更すればいろいろな言葉で言い変えることが出来る。話す言葉の書き方は、音声合成LSIのデータシートの所に文法が書かれているので、これを参考にして記載するのが良い。

《使用部品》
①Arduino UNO互換品 : Arduino UNO R3 (keyestudio)互換品 1個
              Amazonで1020円ほどの値段で購入できる。

②赤外線受光器(1個): PL-IRM2121(38kHz)  秋月電子通商より購入
          データーシート Vcc=5V、220Ω抵抗と4.7μF コンデンサが必要

③音声合成LSI(1個): ATP3012F6-PU (女性の音声明瞭版) 秋月電子通商より購入
          データーシート 

 (注意) ATP3012 と ATP3011の主な違いがこのデータシート17ページに記載されている。動作クロックが外付けになり、セラミック振動子などの外付けデバイスが必要。また、AOUT/PMOD1の端子が入れ替わっているなど変更点があるので注意を要する。

④セラミック振動子(1個) : コンデンサ内蔵タイプ 20MHz ㈱村田製作所製 秋月電子通商より購入

⑤その他部品、
 1kΩ、220Ω抵抗(各1個)と 4.7μF コンデンサ(1個)、
Arduino用ユニバーサルプロトシールド基板(1枚)ピンソケット 1x6 (1枚)、1x8 (2枚)、1x10 (1枚)、ミニブレッドボード BB-601(1個)、ジャンパーケーブル用として 耐熱通信機用ビニル電線 導体系0.65mm 単芯


《Sketch》

// TV リモコンからチャネル番号を読み取り他の制御に使う
#define IR_PIN      2    // 赤外線受信モジュール接続ピン番号
#define DATA_POINT  3    // 受信したデータから読取る内容のデータ位置 東芝REGZA TV

#include <AquesTalk.h>
#include <Wire.h>        // ライブラリ内部で使用するので定義必要

AquesTalk atp;         // インスタンス定義

void setup()
{
    Serial.begin(9600) ;    // パソコン(ArduinoIDE)とシリアル通信の準備を行う
    pinMode(IR_PIN,INPUT) ;  // 赤外線受信モジュールに接続ピンをデジタル入力に設定
}

void loop()
{
    int ans ;
     
    ans = IRrecive() ;       // 赤外線リモコンのデータを受信する
        if (ans != 0) {
            Serial.println(ans,HEX) ; // REGZA TV リモコンからデータを受信し番号に準じて発音
            switch(ans) {
            case 0x01: 
                Serial.println("ただいま解析中") ;
                atp.Synthe("tadaima/kaisekichuu.");
                break ;
            case 0x02:
                Serial.println("脈拍が上昇しています") ;
                atp.Synthe("myakuhakuga/jyoushoushiteimasu.");
                break ;
            case 0x03:
                Serial.println("ゆっくりしていってね") ;
                atp.Synthe("/yukkuri_siteittene?");
                break ;         
            case 0x04:
                Serial.println("よろしいですか?") ;
                atp.Synthe("yorosi'ide_suka?"); 
                break ;
            case 0x05:
                Serial.println("チャイム音J") ;
                atp.Synthe("#J");
                break ;
            case 0x06:
                Serial.println("チャイム音K") ;
                atp.Synthe("#K");
                break ;
            case 0x12:
                Serial.println("連続チャイム音") ;
                atp.Synthe("#K");
                atp.Synthe("#K");
                atp.Synthe("#K");
                atp.Synthe("#J");
                break ;
           }
     }
}
// 赤外線リモコンのデータを受信する処理関数
int IRrecive()
{
     unsigned long t ;
     int i , j ;
     int cnt , ans ;
     char IRbit[64] ;

     ans = 0 ;
     t = 0 ;
     if (digitalRead(IR_PIN) == LOW) {
          // リーダ部のチェックを行う
          t = micros() ;                           // 現在の時刻(us)を得る
          while (digitalRead(IR_PIN) == LOW) ;     // HIGH(ON)になるまで待つ
          t = micros() - t ;                       // LOW(OFF)の部分をはかる
     }
     // リーダ部有りなら処理する(3.4ms以上のLOWにて判断する)
     if (t >= 3400) {
          i = 0 ;
          while(digitalRead(IR_PIN) == HIGH) ;     // ここまでがリーダ部(ON部分)読み飛ばす
          // データ部の読み込み
          while (1) {
               while(digitalRead(IR_PIN) == LOW) ; // OFF部分は読み飛ばす
               t = micros() ;
               cnt = 0 ;
               while(digitalRead(IR_PIN) == HIGH) {// LOW(OFF)になるまで待つ
                    delayMicroseconds(10) ;
                    cnt++ ;
                    if (cnt >= 1200) break ;       // 12ms以上HIGHのままなら中断
               }
               t = micros() - t ;
               if (t >= 10000) break ;             // ストップデータ
               if (t >= 1000)  IRbit[i] = (char)0x31 ;  // ON部分が長い
               else            IRbit[i] = (char)0x30 ;  // ON部分が短い
               i++ ;
          }
          // データ有りなら指定位置のデータを取り出す
          if (i != 0) {
               i = (DATA_POINT-1) * 8 ;
               for (j=0 ; j < 8 ; j++) {
                    if (IRbit[i+j] == 0x31) bitSet(ans,j) ;
               }
          }
     }
     return( ans ) ;
}

《参考にしたブログ》
①音声合成LSIの使い方
 「N.Yamazaki's blog」 Arduino用 音声合成LSIライブラリの利用方法記述部分から、「Arduino_AquesTalk_Library」をDownloadし、簡単な使い方を参考にスケッチ作成を行った。

②赤外線通信について
 「赤外線通信の実験パート2」
(赤外線リモコンを送信器にして何か動かす)
 ・赤外線受信のサンプル回路及びスケッチプログラムを参考に、こちらの状況に合わせて修正するための参考にさせて戴いた。


 ・赤外線受信の回路は、使っている素子が違うため、素子のデータシートを参考にして、使う抵抗とコンデンサを配置し、信号の解析を行った。
 そのためのスケッチは、
赤外線受信モジュール接続ピン番号は同じく「2」に設定し、シリアルポートを此方の状況に合わせただけで、エラー無く稼働させることが出来た。

 ・赤外線リモコンからのデータ受信では、ダウンロードさせて戴いたスケッチに対して、
 IRピンの番号は同じ、受信したデータから読取る内容のデータ位置を「3」に変更して使うことが出来た。

 ・switch文の分岐で、caseの後にSerial.print文を追記して分岐が正しく行われたか否かをシリアルモニタで確認できるようにした。


《参考資料》
①赤外線受光器 使用部品の行を参照の事。
②音声合成LSI ATP3012F6-PU 使用部品の行を参照の事。

04/28/2017

Deek-RobotのData logging shield V1.0を使ってArduino Spyカメラ

Ethernet Shield を使った通信がうまく行かないので、Deek-Robot の Data logging shield V1.0 を使ってこのシステムの小型化を図ってみた。写真のようにコンパクトに出来上がっているが、基本的な回路図は、前回のEthernet Shield を使ったものとほぼ同じで、違っているのは Deek-Robot の Data logging shield V1.0 の CSピンが10 なので、この変更を行っている。
4c4dcb61529951a4016055086123b004 Img_20170428_125755 Img_20170428_125816 Img_20170428_125902 コンパクトに納めるために、僕はシールドの中ほどに写真のように棚を設けて部品を配置した。



Img_20170428_205246 Img_20170428_205416Img_20170428_204948_2 一番下の階層は、Arduino UNOで、第二階層が Deek-Robot の Data logging shield V1.0 で、その空いたボード部分に、PIRセンサ(SainSMART製Pyroelectric Infrared PIR Motion Sensor Detector Module for Arduino)(保持時間:約8秒~15分以上、半固定抵抗[Tx]で調整、最大検知距離:7m(気温等の環境条件による)、半固定抵抗[Sx]で調整)、小型TTLシリアルJPEGカメラ(NTSCビデオ出力付 いわゆる VC0706 )[adafruit PRODUCT ID: 1386]、電子ブザー(PB04-SE12HPR)、LEDを配置した。


LEDについて、
写真左側の青いLEDは、PIRが検出中に点滅し、検出した際には点灯する。右側の赤いLEDはSDカードにデータを書き込み中に点灯する。書き込みが終われば消灯する。
半田付けしてから気づいたが、LEDの取付けはカメラの向く方向に足を曲げられるように配置すべきだった。また、電流が多く流れる為明るいが、少し省エネを狙い、10kΩの抵抗を入れた。後で直しておこう。


写真のファイル名称
は、
 PIC_X.JPG という名前で保存される。
 システムが稼働して第1枚目の写真が撮れない。タイミングの問題か?
 

SDカードは、
 手持ちの下記カードで書き込みOK
  microSD
 2GB
  SDHC ⑩ 4GB, 8GB
   
  で撮影出来た。
 写真の1枚当たりの容量は、320x240サイズの場合 約12KBである。



Img_20170519_161530 Img_20170519_162101左の写真のように、100均ショップでお弁当箱を購入してここにシステムとバッテリーを合わせて入れた。
当初、前述の写真にあるようにシステム全てを弁当箱の中に入れたが、このケースの蓋は約3㎜の厚みが有り、PIRセンサーが稼働してくれなかった。そのため、弁当箱の蓋の部分に穴を明けて、センサーの樹脂ドームのみ顔を出すように改造した。電池は18650 3200mA 2本を直列にして自立型で左写真の右側のように設置した。しかし、5~6時間ほど経過した後で設置場所を確認に行くと、検出ランプが停まってしまった。
システムに流れている電流値を測定すると、約135mAあり、約0.7Wの消費電力にあることがわかった。これでは約5時間しか持たなかったことが理解できる。このシステムで続ける場合、電源の容量をアップする必要がある。AC電源からとるようにするか、大容量のバッテリーに繋ぐか・・・検討する必要がありそうだ。


<<Sketch上の注意点>>

①Deek-Robot の Data logging shield V1.0のピン配置
  D10 - Chip Select これに合わせて前回のスケッチを修正した。
  D11 - SPI MOSI
  D12 - SPI MISO
  D13 - SPI SCK

②スケッチで読み込むライブラリーは、下記のみを「library」の中に置く事とする。
 ・JPEGCamera
     このライブラリーは、ここにあるので、zipファイルでダウンロードしてくること。
③シリアルモニターの通信速度(シリアルモニターの画面右下部)は、38400bpsに設定すること。

④Warningメッセージは出るが放置。システムの稼働には問題無いようだ・・・。

⑤僕が使っているPIRは、前述したものだが、SxとTxの調整によっては、このスケッチで旨く動かない可能性もある。
 僕は、Sxを右一杯近くまで回し、抵抗値は凡そ620kΩの辺りに、ただし、検出できる距離がどの程度になっているかは、調べることが出来なかった。
 Txはセンサーの保持時間を決めるもので、Tx ≒ 24567×R10×C6(=0.01μF) の計算式で計算できる。僕は半固定抵抗を左回りにほぼ一杯近くまで回して、約820Ωの値にした。Txは約0.2秒になる。

それで、下記のスケッチのPIRによる検出部分周辺を一部書き直した。(2017/05/21)


<<Skech>>
/**
 * VC0706 Camera Module Arduino Compatible:
 *  + cvbs: N/A
 *  + 5V: connected to arduino => pin 5V0
 *  + TX (OUT): connected to the RX (IN) of arduino => pin 2
 *  + RX (IN): connected to the TX (OUT) of arduino => pin 3
 *  + GND: connected to the GND
 *  set the serial monitor 38400bps when use it
 *  PIR sensor: connected to arduino => pin 6 
 */

#include <SoftwareSerial.h>
#include <SPI.h>
#include <SD.h>
#include <JPEGCamera.h>

const int chipSelect = 10;  // I changed this CS value
int pirPin     =  6;  // PIR sensor
int pirState =  LOW;  // states of PIR at the initial
int buzzerPin  =  5;
int ledPin1    =  8;  // PIR LED Flashing(ready) ⇒ Lighting(detected)
int ledPin2    =  7;  // SD Light off ⇒ Lighting(busy)

File testFile;
SoftwareSerial s(2, 3);
JPEGCamera cam(s);
char filename[15];
int num = 0;
int numOfBytes;

int photoSize = 1; // 0 -> 160x120 / 1 -> 320x240 / 2-> 640x480
unsigned long mainStartTime, mainEndTime, startTime, endTime;

void setup()
{
//activate 5V for the SD Card shield
  Serial.begin(38400);
  Serial.println("Start of SPY Camera SetUp");
  pinMode(   pirPin,  INPUT); 
  pinMode(  ledPin1, OUTPUT); 
  pinMode(  ledPin2, OUTPUT); 
  pinMode( buzzerPin,OUTPUT);
  digitalWrite(ledPin1, LOW);       // LED:OFF
  digitalWrite(ledPin2, LOW);       // LED:OFF
  
  Serial.begin(38400);
// Wait for the serial port to be opened
   while (!Serial) delay(25);
 
//check SD card
  while (!SD.begin(chipSelect))
    {
      Serial.println("Error: SD initialization failed. Is SD card in the card folder?");
    }
      Serial.println("Info: SD Initialization complete,");

// Serial port connected to the cam
    s.begin(115200); //for VC0706 cam
    delay(50);

    Serial.println("Info: Reseting cam...");
// reset cam and wait.
    cam.reset();
    delay(3000);

    Serial.println("Info: Changing baud rate...");
// change baud rate to work at 3.3v
    cam.chBaudRate(2);// 0 -> 9600 / 1 -> 19200 / 2 ->  38400 / 3 -> 57600 / 4 -> 115200
    delay(50);
  
//change serial to match new camera baud rate
    s.end();
    s.begin(38400);
    delay(50);

  Serial.println("Info: Changing picture size...");
//change picture size
  cam.chPictureSize(photoSize);
  delay(50);

  Serial.println("Info: getting last pic num for the created files...");
  num = getNewSDFileNum();
  Serial.println("Info: Setup is DONE!");
  Serial.println("Start Detecting");
}

void loop()
{
  while(digitalRead(pirPin) == LOW )
  {
    digitalWrite( ledPin1, HIGH);
    delay(100);
    digitalWrite( ledPin1,  LOW);
    delay(100);
  }

  pirState = digitalRead(pirPin);   // read the state of PIR sensor
   digitalWrite(  ledPin1, HIGH);   // LED :ON
   digitalWrite(buzzerPin, HIGH);   // Warnig Buzzer ON                         
   Serial.println("Some movements were detected!");
   takePic();
}

void takePic()
{
  Serial.println("Info: Preparing to take new photo.");

// must call stopPictures before a new photo is taken.
  cam.stopPictures();
  delay(50);

// take photo
  digitalWrite(buzzerPin,  LOW);   // Buzzer OFF
  digitalWrite(  ledPin1,  LOW);   // LED:OFF 
  cam.takePicture();
  delay(50);

  num++;
  sprintf(filename, "PIC_%i.JPG", num);

  if (SD.exists(filename)) {
    SD.remove(filename);
  }

// Create file
  digitalWrite( ledPin2, HIGH);     // LED :ON  
  testFile = SD.open(filename, FILE_WRITE);
  
//save to file
    Serial.print("Info: Saving photo to SD: ");
    Serial.println(filename);
    
    numOfBytes = cam.readData(testFile);

    Serial.print("Info: Bytes for photo: ");
    Serial.println(numOfBytes);

//must close file to finish writing.
  testFile.close();
    digitalWrite( ledPin2,  LOW);     // LED:OFF
    Serial.println("Info: saved picture that detected!");
}

int getNewSDFileNum() {
  File root = SD.open("/");
  int n = getFileLastNumForDirectory(root);

  root.close();
  return n > 0 ? n : 0;
}

int getFileLastNumForDirectory(File dir) {
  int n = 0;
  int fn = 0;

  while (true) {
    File entry =  dir.openNextFile();

    if (!entry) {
// no more files
      break;
    }

    fn = 0;

    if (entry.isDirectory()) {
      //fn = prepareFileLastNumForDirectory(entry);//no need to be recursively
    }
    else {
      String name = String(entry.name());

      if (name.startsWith("PIC_") && name.endsWith(".JPG")) {
        int pos = name.indexOf(".JPG");
        name = name.substring(4, pos);

        if (isValidNumber(name))
          fn = name.toInt();
      }
    }

    if (fn > n) {
      n = fn;
    }

    entry.close();
  }

  return n;
}

boolean isValidNumber(String str) {
  if (str && str.length() > 0) {
    for (byte i = 0; i < str.length(); i++)
    {
      if (!isDigit(str.charAt(i))) return false;
    }
    return true;
  }
  return false;
}

より以前の記事一覧

September 2017
Sun Mon Tue Wed Thu Fri Sat
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Recent Trackbacks

無料ブログはココログ