ソロでたのしむ

キャンプ、電子工作、DIY、乗り物などの趣味を紹介

テント内で焚き火!っぽいランタンの炎を楽しむ

キャンプで、そろそろ寝ようかな〜という時に、大自然に囲まれていた雰囲気とテント内の雰囲気のギャップに、少しガッカリしたことがあります。

火器が使えないテント内では、LEDの灯りなどを使うことになりますが、目に刺さるような白色に、日常を感じてしまい、焚き火の炎に照らされて醸成されていたリラックスモードが、一気に無くなってしまったように気分に...

一方で、このところキャンプに行っていないどころか、買い物だけしか外出していませんが、このような時だからこそ!ということで、「外でもお家でもキャンプ」気分を盛り上げる小道具を増やそうと思います。

そこで今回は、テント内でも使える「焚き火の炎」を作って、どこでもキャンプファイヤーの光を、手軽に楽しめるようにしてみます!

テント内で焚き火!っぽいランタンを楽しむ

 

ランタンを見つめ続けた結果!

キャンプ用のLEDランタンは、停電などの非常時にも使えますよね!

私は、屋内ではいつも目につく場所に置いて、来たるべき次回のキャンプに備えて時々充電したり、来てほしくない非常時に備えたりしています。

キャンプや夜釣りで活躍していたLEDランタンですが、唯一の欠点が、明るすぎることです。

キャンプ用LEDランタンを改造します

焚火などの穏やかな明かるさに慣れた状態で、この光量を直視してしまうと、目が焼けるかのように感じます。

もっとキャンプで使いたくなるランタンにしたいと思いつつも、キャンプに行かない室内生活で、視野に入れるように置き続けていて、ふと思いつきました。

のように光らせたら、室内とキャンプのどちらでも、良い雰囲気作りができる!

少し前になりますが、フロアランプを焚き火風に光らせることができたので、それより少ないLEDを使えば良いランタンの改造は、難しくはない筈です。

早速、炎をランタンに封入してみましょう。

LEDランタンを改造

炎の光パターンを作るために、5Vで駆動するマイコンを使います。

2年ほど使い続けているランタンは、もともと白色LEDが使われており、またUSB(5V)で充電できるものなので、電源系はそのまま使えそうです。

炎のランタン作成に使うもの

まずは、WS2812Bが一定間隔で配置されているカラーLEDテープを準備しました。

WS2812Bで炎を作ります

写真には2種類のものが写っています。

今回は、少ないスペースにより多くのLEDを埋め込みたいので、外側でぐるぐるっとしているLEDの間隔が少ないものを使います。

材料として他に入手したのは、炎のパターンを作るために働いていただく、Arduinoだけです。

配線類などの副資材は、既にあるものや廃材を利用します。

LEDランタンの中身

自己責任!となるので、少しドキドキしながら、LEDランタンを開封します。

赤色の配線がプラス、青色の配線がマイナス...かと思いきや、一箇所だけ赤色のマイナス線がありますね。

(自己責任で)LEDランタンを分解
(自己責任で)LEDランタンを改造
  • 茶色の基盤;電源管理
  • 青色の円柱;リチウム電池
  • 緑色の基盤;スイッチ管理とLEDの定電流化

ということのようです。

回路の電圧を測ってみると、4V以上でている様子で、ギリギリですが思惑どおりArduinoも動かせそう!

本来であれば電圧変換すべきですが、とりあえず、オリジナルの回路をそのまま使って、様子をみましょう〜

ただし、もともとのLEDには定電流ダイオードを通して電源が供給されていましたが、ArduinoとWS2812Bには、定電流ダイオードを通さずに電源供給して、たくさん働いていただきます。

大まかな回路構成がわかったので、もともとのLEDWS2812Bに交換して、光り方を制御するArduinoを追加する作業に進みます。

ArduinoとWS2812Bを配線しましょう

LEDランタンには白色LEDが3本使われていたので、同じ長さになるように切断したWS2812Bテープ3本を、直列で配線します。

今回使うのは7個x3列の、合計21個ですね。

WS2812Bを3列に並べて使います
Arduinoをランタンに埋め込む!

ArduinoからWS2812Bへの信号線は、黄色の配線1本のみです。

他には、それぞれに電源線2本を接続するだけ!

作業の途中で、Arduinoのデジタルピンではなく、アナログピンに信号線をハンダ付けしてしまうというトラブルがあったものの、サクサクっとハンダ付けを終わらせて、準備しておいたプログラム(スケッチ)を書き込みます。

WS2812Bの炎パターン
ランタンのLEDをWS2812Bに改造

さて、電源を入れて試運転してみましょう。

...していたとおりに光っています!

いつも思うのですが、この瞬間の安堵感と嬉しさは、格別です〜

しっかり動くことがわかったので、後はランタンを組み立て直すだけですが、より炎っぽさを表現するために、シェード様のものを追加しておきます。

LEDの点光源感がバレてしまうと炎としては不自然なので、ボカシを入れるイメージです。

ランタンにキャンプファイヤーを封入

シェードには、よくある梱包材を廃材利用しました。

右の写真はシェード1枚での発光の様子ですが、まだ点光源感が残っていたので、左の写真のように二重のシェードを入れることにしました。

焚火風ランタンを自作

完成状態での点灯チェック!

本物の炎のようで、しばらく見入ってしまいました。

光り方は、動画でもご確認いただけます。

回路

今回つくる回路は、一か月ほど前に作った光ファイバーアートの時と、WS2812Bの個数を除いて全く同じです。

www.solocamptouring.com

回路図は、こちらで紹介しています。

WS2812Bは、配線数が少なくていいですね!

作業風景は、早送りですが動画でも紹介しています。

youtu.be

スケッチ

ご参考で、Arduinoのスケッチ(プログラム)です。

WS2812Bを使うため、GitHubのFastLEDライブラリを使用させていただきます。

炎の発光は、Fire2012WithPaletteのスケッチを応用しました。

#include "FastLED.h"
#define DATA_PIN    5
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define HEIGHT 7
#define WIDTH 3
#define NUM_LEDS HEIGHT*WIDTH
#define lighting_PER_SECOND 30 // Mainly for fire frame pattern

CRGB leds[NUM_LEDS];
CRGBPalette16 gPal;

int BRIGHTNESS = 20;
int scaleVol = 230; // Scale the heat value from 0-255
int SPARKING = 220; //50-200 out of 255
int COOLING = 50; // 20-100: Less = taller flames.  More = shorter flames. 

bool gReverseDirection = false;

void setup() {
  delay(1000); //delay for recovery
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
}

void loop()
{
  gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White);
  static uint8_t heat[NUM_LEDS]; // Array of temperature readings

  // Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / HEIGHT) + 2)); //heat-randam8 with a floor of 0
    }
  
  // Heat from each cell drifts 'up' and diffuses a little
    for( int k= HEIGHT - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
      heat[2*HEIGHT-1-k] = (heat[2*HEIGHT-1-k + 1] + heat[2*HEIGHT-1-k + 2] + heat[2*HEIGHT-1-k + 2] ) / 3;
      heat[2*HEIGHT+k] = (heat[2*HEIGHT+k - 1] + heat[2*HEIGHT+k - 2] + heat[2*HEIGHT+k - 2] ) / 3;
    }
    
    //  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int x = random8(6);
      int y ;
      if(x > 3){
        y = 2*HEIGHT-4+x;
      } else if(x > 1){
        y = 2*HEIGHT+1-x;
      } else {
        y = x;
      }
      heat[y] = qadd8( heat[y], random8(160,255) ); //heat+randam8
    }

    // Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      uint8_t colorindex = scale8( heat[j], scaleVol);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      if( gReverseDirection ) {
        if(j < HEIGHT){
          pixelnumber = (HEIGHT-1) - j;
          }else if(j < 2*HEIGHT){
          pixelnumber = HEIGHT+j;
          }else{
          pixelnumber = (3*HEIGHT-1) - j;
          }
      } else {
        pixelnumber = j;
      }
      leds[pixelnumber] = color;
    }

// Let's light them now
  random16_add_entropy( random());
  FastLED.show();  // send the 'leds' array out to the actual LED strip
  FastLED.delay(1000 / lighting_PER_SECOND);
// End of lighting
  
}

以前に作ったフロアランプはWS2812Bを4列配置しましたが、今回のランタンは3列です。

また、今回は発光パターンを赤色の炎だけに限定したので、ランタンのスケッチは、フロアランプに比べて、たいへん短くなっています。

最後に、ランタンとフロアランプの炎を並べて、記念撮影

どちらも、なかなか良い雰囲気で、他の明かりを消して、室内で楽しんでいます!

夜に外から見たら、怪しいと思われているかもしれませんね。

WS2812Bで、炎パターンで光るライトを自作

光ファイバーの夜景アートが欲しい!

前回に引き続き、お部屋で楽しむノスタルジーシリーズです。

きらきらと、小さな光点が点滅したり色を変える夜景のアート、数十年前に流行りましたよね!

思い返せば、その頃から自作好きだったようで、夜のブルックリン橋のポスターを買ってきて、背景のビルの窓に、光ファイバーを一本づつ埋め込んだな〜と思い出します。

最近になって、この夜景アートが欲しくなり、ネットなどで調べているのですが、なかなか見つかりません。

薄型ディスプレイが出回っている昨今では、流行らないのでしょうか?

入手しづらいとわかると、あの幻想的にも感じた光が、どうしても欲しい!との思いが、なおさら増してきます。

またまた、無いものは作るしかない!ということで、お部屋で過ごす時間を使って、自作してみます。

Fiber optic picture DIY

夜景のポスター

今回の「光る夜景アート」のDIYにあたり、最初にベースとなる夜景のポスターを探してみました。

...しかし、私が探しているA3サイズの大きさで、感性を揺さぶるようなポスターは、なかなか見つかりません。

空が真っ赤に染まっているような夕焼けの景色はあるのですが、どちらかというと日が沈み切った後の景色が欲しい!

まあ、慌てずに探しましょう~と、1か月ほど構想を温めていた頃、普段から行くことがあるホームセンターの奥の方で、一枚の絵に目が止まりました。

タイで見つけたオーロラのアートフレーム

何、このオーロラの発色? 綺麗すぎます!

夕焼けに染まる街のアートフレームもありましたが、このグラデーションがかかったグリーンの空から、目が離せません。

10分ほど絵を眺めてつつ妄想にふけっていて、いい事を思いついてしまいました。

このオーロラをで光らせたら、おもしろそう!

お手頃価格で、DIYにちょうどいい大きさのこのアートフレーム、即購入しました。

ちなみに、このアートフレームは時計です。

オーロラのアートフレーム

オーロラが発生している夜空に、くっきりと時間が表示されていて、少し違和感がありますね。 

お家に帰ってきてアートフレームの中身を確認した時に、時計機能は撤去することにしました。

アートフレームの中のデジタル時計

時計を埋め込むのにちょうどいい入れ物が見つかったら、時計ユニットは再生いたします。

街の夜景とオーロラを同時に楽しむ

一目惚れしたアートフレームには、山の上空のオーロラに照らされて、海沿いの人里が描かれています。

漁師町といった雰囲気でしょうか? 私の勝手なイメージでは、カニ漁の漁船が係留されている筈です!

今回は、LEDから光ファイバーケーブルで導光して漁師町をライティングすると共に、オーロラはLEDの光を裏から当てます。

もうすっかり使い慣れたカラーLED、WS2812Bが25個直列で繋がったテープを使って、最初の3個は漁師町、残りの22個はオーロラ用とします。

ARDUINOで、ファイバー ライト ピクチャーを自作

山の影を作りましょう

アートフレーム全体は、キャンバス生地の裏側から光らせます。

夜空に、山の稜線が漆黒の姿を映すようにしたいので、山の輪郭に沿って、板を入れておきます。

この板は、光ファイバーケーブルの光源を取り付けるスペースとしても、活躍してもらいます。

街の夜景

街といっても、漁師町なので、派手すぎず、かといって寂しくは感じない程度の光りをつくります。

光源となるLED3個は、ランダムなタイミングで、それぞれの色を変えていきます

数えてはいませんが、たくさんの光ファイバーケーブルの端末を、町の光りとして埋め込んで、反対側の端末は、LED3個の光源近くに束ねました。

光ファイバーケーブルで光るアートを作りましょう

夜空に揺れるオーロラ

アートフレームの上方に22個のLEDを並べて、徐々に色を変えつつ、光量のゆらぎを作ります

個々のLEDごとに色と光量を制御できるWS2812Bのメリットをいかした表現で、実際のオーロラをイメージして、光量のゆらぎの大きさ発生頻度などをトライ&エラーで決めます。

瞬く星も、この光源を使って、数個つくっておきました。

WS2812B LEDでアートを光らせる

回路図

回路と言っても、ものすごく簡単なものです。

手動で光量や色を変更することも無いので、可変抵抗(ボリューム)も使いません。

回路図はFritzingで作成しました。

LED lighting circuit for Fiber light picture

回路の作成シーンや、アートフレームの改造風景は、動画でも紹介しています。

youtu.be

参考;夜景アートを光らせるArduinoこスケッチ(プログラム)

簡単な命令文で、綺麗な光を演出できるGitHubのFastLEDライブラリを使わせていただきます。

おかげさまで、随分と短いスケッチになりました。

#include "FastLED.h"
#define DATA_PIN    5
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define HEIGHT 25              //  aurora 22 + village 3
#define WIDTH 1
#define NUM_LEDS HEIGHT*WIDTH
#define lighting_PER_SECOND 10 // aurora pattern changr per sec.

CRGB currentleds[NUM_LEDS];
CRGB leds[NUM_LEDS];
int BRIGHTNESS = 160; //out of 255
int auroraSparkMax = 90; // aurora spark volume 0-100 out of 255 -BRIGHTNESS
int auroraSparkMin = 60; // aurora spark volume 0-100 out of 255 -BRIGHTNESS
static uint8_t heat[NUM_LEDS]; // Array of temperature readings
uint8_t gHue = 1; // starting color out of the patterns 0to255  


void setup() 
{
  delay(1000); //delay for recovery 
  FastLED.addLeds<LED_TYPE, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void loop()
{
 unsigned long startMillis= millis();  // Start of sample window
 FastLED.setBrightness(BRIGHTNESS); 
 fadeToBlackBy( leds, NUM_LEDS, 200);
 
// make aurora and village light
  // aurora each cell heat slides
  EVERY_N_MILLISECONDS( 200 ) {
    for( int i = NUM_LEDS-1; i > 3; i--) {
     heat[i] = heat[i-1]  ; 
     }
     heat[3] = random8(auroraSparkMin, auroraSparkMax) ;
  }
     for( int j = 3; j < NUM_LEDS; j++) {
     leds[j] += CHSV( gHue + j, 255, heat[j]);
    }


  // village color

  EVERY_N_MILLISECONDS( 100 ) {
    for( int j = 0; j < 3; j++) {
    heat[j] = BRIGHTNESS  ;   
     int ColorChange = random8(250);
     if (ColorChange < 1){
     uint8_t gHueVillage = gHue + 127; 
     leds[j] += CHSV( gHueVillage, 255, heat[j]);
     currentleds[j] = leds[j] ;
     }else{
     leds[j] = currentleds[j] ;
     }
     uint8_t colorindex = scale8( heat[j], 255);
     }
  }
  
// Let's light them now
  FastLED.show();  // send the 'leds' array out to the LED strip
  FastLED.delay(1000 / lighting_PER_SECOND);
  
  EVERY_N_MILLISECONDS( 500 ) { 
    gHue++ ;
  }
  
// End of lighting

}

オーロラと街の光は、どちらも徐々に色を変えていきますが、意図的に違う色になるようにしています。

どちらかが緑色(に近い色)の時は、もう一方は紫色(に近い色)といったイメージです。

ファイバーライトピクチャーの自作

夜景アートというと、大都会の景色を連想しますが、偶然出会ったオーロラの景色に一目惚れしたことで、今回は一風変わった夜景アートを作ることになりました。

私としては、一本のLEDテープを光源に使い、オーロラと街の夜景を上手く表現できたと感じています。

このところ続いているお家で楽しむシリーズですが、今回は、懐かしさが漂うファイバー ライト ピクチャーを自作してみました。

スペースレールのモーター音を静かにしたい!

Light up the SPACERAIL

こんにちは。

皆様は、お家時間をいかがお過ごしでしょうか?

私は最近、お家に籠っている時間を使って、

スペースレールという知育おもちゃを、少しだけ改造して組み立てました!

「そういえば昔に持っていた!」と、懐かしく思い出される方も多いと思いますが、おそらくそれは、1983年くらいから5年ほど販売されていたスペースワープですね!

私も相当昔にスペースワープを組み立てたことがありますが、大人になった今回は、アップデートされて蘇ったスペースレールに、少し改造を織り込んで作成してみます。

お部屋のオブジェとして動かし続けるには、どうすれば良い?という観点で、小技も繰り出します!

SPACERAILのモーターの音を静かにしましょう!

スペースレール(SPACERAIL) 

スペースレールは、上の写真のようにレールを組み立てて、直径1/2インチ(1cmくらい)のボールを、上手く転がそう!という、パズル的な要素がある玩具です。

組み立てキットは14種類あり、難易度レベルが9段階で付与されています。

組み立て時間は、最も簡単なもので1時間、最高難易度のものだと60時間くらいかかるらしいとのこと!

スペースワープの時代には、レベル3に相当するものを組み立てたと記憶していますが、今回はレベル4.1に挑戦します。

大人の楽しみ方

さて、お部屋のオブジェにしましょう!というコンセプトで作るスペースレールは、スペースワープ時代の記憶を辿ると、2点ほど改良したくなります。

  1. エレベーターからボールが落ちることがある
  2. エレベーターを動かしているドライブユニットの音が大きい

f:id:solocamptouring:20210725161634j:plain

1 は、エレベーターの途中にあるボールが、他のボールがエレベーターに突入してくる時の衝撃で落ちることが多かったと思いますので、なんらかの落下防止ガードを付けたいと思います。

については、手の込んだ改善が必要だと思われます。

DCモーターと、その動力をエレベーターに伝えるギアの双方が、ウィ~ンという大きめな音を発生する原因となっている様子...

Inside the drive unit of Spacerail

静かな部屋で動かし続けるためには、少し耐えがたいドライブ音は消してしまって、ボールが転がる音と、エレベーターにボールが吸い込まれる瞬間の音だけにしたい!

静かに動かすのであれば、ステッピングモーター化がお手軽でしょう!ということで、モーターの種類を変えて、静音化してみます。

ステップモーター :  28BYJ-48 

ステッピングモーターは、速度(回転数)や位置を制御しやすいので、3Dプリンターにも使われていますね。

今回使うステッピングモーターは、ドライバーとセットで売られていることが多い28BYJ-48です。

ステップモーター 28BYJ-48をSpacerailに仕込む

上の写真で、右に写っているのが、そのステッピングモーターです。

スペースレールのドライブユニットのハウジングに収まりそうな、ちょうどいい大きさですね。

ドライバーとセットで安価に入手できる28BYJ-48は、マイコンボードで簡単に動かせます。

私がよく使うマイコンッボードは、Arduino Nanoという、小さいタイプのものです。

少しArduinoに興味がでてきた!という方は、使い方などを纏めている記事があるので、よろしければ参考にしてみてください。

www.solocamptouring.com

早速、必要な部品を入手して、ステッピングモーター化に取り組みます。

モーター交換のためのハンダ付け作業や、ハウジングの加工を終えて、静か~に動くドライブユニットが完成しました。

スペースレールを、ステップモータで動かす
Spacerailの、静かに動くドライブユニットが完成

作業の様子は、早送りですが、動画内で紹介しています。

ステッピングモータを動かす回路図

Fritzingで作成した回路図を紹介いたします。 

作るのが大変そうに見えるかもしれませんが、回路図の右半分は、ステッピングモータと、そのドライバなので、既製品です。

DCモーターからステッピングモーターに改造 ARDUINO使い方

この回路では、USB接続の5V電源を使います。

ハンダ付け作業も少なく、配線をつなぐと、下の写真のような状態になります。

Arduinoとスッテプモーターで、スペースレールを動かす

スペースレールを組み立てましょう

ドライブユニットの試運転して、満足できるレベルの静音になっていることを確認したら、いよいよスペースレールを組み立てます。

説明書を見ながら、まずは支柱を立てていきます

スペースレールを組み立てます
Spacerail installation

1時間以上かけて、支柱の作業を終えました!

次に、レールを切ってから、取り付けていきます

説明書で指示されているレールの長さは少し余裕を持たせているようで、実際に装着した後で余った部分をさらに切断します。

ちなみに、同経路のレール左右でも、余る長さが違ってきます。

スペースレールのレール取付け
Install rails on the Spacerail

2時間以上かけて、レールも完成しました。

途中で、ボールを試走させながらレールを微調整しているので、予想以上に時間がかかります。

これらの作業と、静かに動くドライブユニットの様子は、動画でもご確認いただけます。

youtu.be

ほとんどの作業を早送り再生しているので、動画内では超高速で完成していますね。

エレベータータワーから落ちてしまうボールの対処方法

エレベータの中間地点を登っているボールが、他のボールがエレベータに突入してきた時の衝撃で落ちてしまうことがあります。

もう少ししっかりとボールを固定してくれる設計ならいいのに~」と言っていても仕方がないので、衝撃が加わってもボールが落ちないようにする手立てを考えます。

エレベータータワーから落ちてしまうボールの対処方法

これ、何かわかりますか?

はい! 食品を包んだりする時に使う、台所用ラップの芯です。

らせん状に切れ目を入れて、エレベータータワーの長さ(高さ)に合わせて切断したら、すぽっと入れるだけでボールの落下を防止できます。

ちょうどいい直径のアクリルパイプがあれば、もっと完成度が増すと思われますが、ラップの芯でも、それほど違和感はありません。

お家に在りそうな物での対策としては充分でしょう!

ボールの落下がなくなり、モーターもほぼ無音になったので、お部屋の置き物として、長時間動かし続けられるものになりました。

けっこう時間がかかりましたが、組み立て作業も楽しかった!

今日も、スペースレールの前に椅子を置いて、ボールの動きを目で追い続けています。

さて、次は何を作ってみようかな?

おまけ その1; ライトアップしてみました

テーマパークのジェットコースターを思い起こさせるスペースレール、無事故かつ静音化して、オブシェとして動かし続けることに成功しました。

お部屋で眺めていて、もう少し幻想的にしたいと思い、ライトアップしてみました。

その様子を、約15分間にわたり録画してみます。

youtu.be

録音した音は、全く手を加えていないので、ボールの音だけが聞こえる様子をご確認いただけると思います。

おまけ その2;ステッピングモータを動かすスケッチ(プログラム)

ご参考で、スケッチを紹介いたします。

ステッピングモーターのドライバーに対して、コイルに順番に電気を流しなさい~と命令しているだけの、簡単なスケッチです。

int motorPin1 = 4;    // Blue   - 28BYJ48 pin 1
int motorPin2 = 5;    // Pink   - 28BYJ48 pin 2
int motorPin3 = 6;    // Yellow - 28BYJ48 pin 3
int motorPin4 = 7;    // Orange - 28BYJ48 pin 4
int volumePin = 3;
int motorSpeed;
int motorSpeedtimes = 2;

int lookup[8] = {B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001};

void setup() {
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorPin3, OUTPUT);
  pinMode(motorPin4, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  motorSpeed  = 1024 * (motorSpeedtimes + 1) - (analogRead(volumePin) * motorSpeedtimes) ; //0to1023 change into speed
  clockwise();
}

void anticlockwise()
{
  for (int i = 0; i < 8; i++)
  {
    setOutput(i);
      delayMicroseconds(motorSpeed) ;
}
}
void clockwise()
{
  for (int i = 7; i >= 0; i--)
  {
    setOutput(i);
      delayMicroseconds(motorSpeed);
}
}
void setOutput(int out)
{
  digitalWrite(motorPin1, bitRead(lookup[out], 0));
  digitalWrite(motorPin2, bitRead(lookup[out], 1));
  digitalWrite(motorPin3, bitRead(lookup[out], 2));
  digitalWrite(motorPin4, bitRead(lookup[out], 3));
}

それでは、皆さま良いお家時間をお過ごしください!

お家でエレクトリカルパレード

 

いつでも正確な時計を自作。GPSからの情報を秒単位で表示します

こんにちは。在宅時間が増えてきていると思いますが、お元気でおすごしですか?

私は(も)、アウトドアで過ごす予定がたてられない生活をしています。

しかし、そんな時こそキャンプでも使えるガジェットを自作すべき!ということで、在宅勤務でもアウトドアでも使える、いつでも正確な時間を教えてくれる時計を作ってみます。

 

いつでも時間が正確な時計を作ってみる

マイコン「Arduino」は難しくない!

最初に、「デジタル時計なんて自作できるのか?」問題について、少しだけ私の所感を紹介させていただきます。

マイコンを使う工作なんて無理〜と思っていた私が、Arduinoに出会ったのは、数年前です。

その頃すでに、日本語でArduinoの使い方を紹介されているサイトなどがありましたが、専門用語が多くて少し難解だったり、応用しづらかったりして、やはり基礎知識がないと無理なのかな?と最初は思いました。

海外のサイトからも情報を得たりしながら、いろいろ作ることに成功した現在、結論を言うと、「基本的なエクセルの関数計算を使うことができるなら、難しくない!」です。

Arduinoの場合は、プログラムのことを「スケッチ」と呼びますが、スケッチを描くのと同じくらい簡単ですよ!ということのようです。

表計算ソフトのエクセルで「もし〇〇ならば、△△を計算(実行)」の関数を使ったことがあれば、Arduinoスケッチの8割くらいは理解できると思います。

実際に私は、if~else~式を乱用して、スケッチを作っています。残りは数字の種類(整数、桁数)などを少し勉強すれば、だいたい理解できました。

ひとつ作品を成功させると、可能性が無限に広がったような気持ちになって、何かを表示するだけでなく、動かしたり光らせたりする作品など、いろいろ作ってきました。

もし興味がおありでしたら、見てみてください。

www.solocamptouring.com

 

人工衛星の情報で、正確な時間を入手

さて、前置きが長くなりましたが、置き時計を作ります。

最近は、腕時計でも採用されているものがありますが、地球のまわりを飛んでいる人工衛星から、正確な時間情報をいただきます

使う部品はこれだけです。

GPS時計を自作します

  • Arduino
  • NEO-6M GPSモジュール
  • 1602 LCDモジュール
  • 220Ω 抵抗
  • 1kΩ 可変抵抗 

あとは、試作用の汎用基盤などの小物ですね。

GPSモジュールとLCDモジュールは、電力消費がそれほど多くないので、この後で紹介する回路図とは少し違って、USB接続したArduinoからの5Vアウトプットで駆動しています。

GPS時計の回路図

いつもお世話になっているFritzingで回路図を作成しました。

LCDモジュールへの配線数が多いですが、GPSモジュールへの配線はわずか3本です!

これだけで正確な時間情報がいただけるなんて、嬉しいですね。

GPS clock 1604 LCD_回路図

組み立てましょう

はんだ付けって大変!なのですが、便利な道具があると、ずいぶん楽になります。

若かりし頃は、横着なやり方もしていましたが、分別ができる年頃になってから、便利な道具に助けてもらうことを覚えました。

電線どうしのはんだ付けなど、もう一本手が欲しい~と思いながら苦戦していましたが、クリップ付きのはんだこて台があると、本当に楽です。

ハンダ付けを含めた作成の様子は、タイムラプスの動画にしてみました。

youtu.be

完成したことろで、お部屋のテレビ台に置いてみると、こんな感じです。

USBで駆動するGPS置時計

きちんと、秒単位で正確に働いています!

青色の背景に白い文字が浮かび上がっていて、雰囲気も良いですね。

ちなみに、このGPS時計は、電源を入れた後、表示するまで少しだけ時間がかかりますが、一度電波を受信した後は、今のところ表示が止まったりすることはありません。

アウトドアなら問題なく、また置時計として使うにも、電波が受信できる場所であれば、大丈夫でしょう。

GPS時計のスケッチ(プログラム) 

これまで紹介させていただいた作品の中では、おそらく最もシンプルなスケッチです。

バンコク時間に合わせたスケッチになっているので、日本時間を表示する場合は、6行目の

#define time_offset 25200 // define a clock offset of 3600*7 seconds (7 hour) ==> TH = UTC + 7

の部分を、

#define time_offset 32400 // define a clock offset of 3600*9 seconds (9 hour) ==> JP = UTC + 9

にします。 

スケッチは、TinyGPS++と、Arduino Time Librariesのライブラリを、インストールしてから使います。

#include <TinyGPS++.h>    
#include <TimeLib.h>          
#include <SoftwareSerial.h>   
#include <LiquidCrystal.h>    
TinyGPSPlus gps;
#define time_offset   25200  // define a clock offset of 3600*7 seconds (7 hour) ==> TH = UTC + 7
#define S_RX    9
#define S_TX    8
SoftwareSerial SoftSerial(S_RX, S_TX);   
LiquidCrystal lcd(2, 3, 4, 5, 6, 7); // LCD connections (RS, E, D4, D5, D6, D7)
 
// variable definitions
char Time[]  = "00:00:00";
char Date[]  = "2000/00/00";
byte last_second, Second, Minute, Hour, Day, Month;
int Year;
 
void setup(void)
{
  SoftSerial.begin(9600); 
 
  lcd.begin(16, 2); 

// welcome display
  lcd.setCursor(0, 0); 
  lcd.print("   GPS  CLOCK   ");
  lcd.setCursor(0, 1); 
  lcd.print("ARDUINO  POWERED");

  delay(5000);
  lcd.clear()  ;
}
 
void loop()
{
  while (SoftSerial.available() > 0)
  {
    if (gps.encode(SoftSerial.read()))
    {
// get time from GPS
      if (gps.time.isValid())
      {
        Minute = gps.time.minute();
        Second = gps.time.second();
        Hour   = gps.time.hour();
      }
 
// get date from GPS
      if (gps.date.isValid())
      {
        Day   = gps.date.day();
        Month = gps.date.month();
        Year  = gps.date.year();
      }
 
      if(last_second != gps.time.second())  
      {
        last_second = gps.time.second();
        setTime(Hour, Minute, Second, Day, Month, Year);
        adjustTime(time_offset);
 
// update time
        Time[6] = second() / 10 + '0';
        Time[7] = second() % 10 + '0';
        Time[3]  = minute() / 10 + '0';
        Time[4] = minute() % 10 + '0';
        Time[0]  = hour()   / 10 + '0';
        Time[1]  = hour()   % 10 + '0';
 
// update date array
        Date[2] = (year()  / 10) % 10 + '0';
        Date[3] =  year()  % 10 + '0';
        Date[5]  =  month() / 10 + '0';
        Date[6] =  month() % 10 + '0';
        Date[8]  =  day()   / 10 + '0';
        Date[9]  =  day()   % 10 + '0';
 
// show time & date
        print_wday(weekday()); 
        lcd.setCursor(4, 0);    
        lcd.print(Time);         
        lcd.setCursor(0, 1);    
        lcd.print(Date);         
      }
    }
  }
}
 
// for displaying day of the week
void print_wday(byte wday)
{
  lcd.setCursor(11, 1);  // move cursor to column 5, row 1
  switch(wday)
  {
    case 1:  lcd.print("(SUN)");   break;
    case 2:  lcd.print("(MON)");   break;
    case 3:  lcd.print("(TUE)");   break;
    case 4:  lcd.print("(WED)");   break;
    case 5:  lcd.print("(THU)");   break;
    case 6:  lcd.print("(FRI)");   break;
    default: lcd.print("(SAT)");
  }
}

本当に短いスケッチですね~

今回は、簡単に自作できるGPS時計の紹介でした。

早くキャンプに持っていきたい!

電波が届かないキャンプでも正確な時間な時計

それでは最後に、皆様がお家時間もアウトドア時間も楽しまれるよう、祈念しております。

炎も躍るサウンド レベル メーター。IKEAのフロアランプをカラフルに改造

今回は、お家でキャンプ気分シリーズの、スピンオフ編です。

焚き火を眺めてすごす、キャンプでの優雅な時間を、屋内でも楽しみたい!ということで、背が高いフロアランプを、カラーLEDを使って改造して、焚き火台風にしてみます。

 

炎が躍るサウンド レベル メーター。WS2812Bでファイヤーします

焚き火とサウンドレベルメーター

アウトドアを楽しめない週末が続くと、いろいろな思いがこみ上げてきます。

  • 殺風景な部屋に、フロアランプが欲しい!
  • キャンプに行けないのであれば、部屋で焚き火気分を楽しめないかな?
  • 外出できないのであれば、しばらく封印していたDIYでもやってみようか...

こんな妄想たちを、一気に実現するには、焚き火風のフロアランプを自作するしかない!と確信しました。

テーマパークの仕掛けで見かける、本物の炎のような、フェイクな焚き火です。

ファンの風によって布をチラつかせて、さらに光も当てることで、本物の焚き火と錯覚してしまう、あれですね。

購入した方が安くすみそうな気がしますが、自作もできそうな気がします。

 

しかし、構造を考えていて、音について悩みが生じます。

くつろぎの時間を過ごしたいところで、ファンの音に耐えられるだろうか?と。

テーマパークのような、他の音が存在する場所なら気にならないと思いますが、プライベートな部屋ではファンの音は雑音でしかありません。

ファンを使わずに炎のような光を実現するには、小さな光源がたくさん必要になりそう...と考えていて、それならLEDで表現してみましょう!と、コンセプトがみえてきました。

LEDの直接光では、炎のように見せるのは難しいと思われるので、日本の伝統的なフロアライト、行燈の中にLEDたちを閉じ込めてしまいます。

行燈の中に焚き火があっても、いいじゃない!

行燈をサウンド レベル メーターに!

不規則に揺らぐ焚き火の光は、見ていて飽きませんよね〜

あの揺らぎを人為的に表現するには、乱数発生プログラムを使ってLEDを不規則に光らせることが近道だと思いました。

...でも、それだけだと不規則すぎて面白くない気もします。

相変わらずの捻くれ者ぶりを発揮してしまったので、一週間ほど悩んでみました。

時間をかければ閃くこともあるようで、ふと音楽に合わせて光らせてみたら面白いのでは?と思いつきます。

行燈の中で、炎と、音楽に合わせて光が踊っているフロアーランプ!

これは欲しい!と納得できるアイデアが浮かんだので、さっそく制作に取り掛かります。

IKEAで行燈選び

アウトドアには行けない日が続いているものの、家具屋さんのIKEAが家の近くにあり、毎週末のように通って、気分転換をしています。

そんなIKEAには、今回の行燈サウンド レベル メーターの改造ベースにピッタリの、フロアランプがあることを知っています。

IKEAのフロアランプに、カラフルなLEDを仕込もう!

フードの材質が紙なので、光の指向性があるLEDとの相性もバッチリです。

いつもIKEAでは散歩スピードでショールームを楽しんでいますが、今回は売り場に直行して、フロアランプを入手してきました。

Arduinoと接続するモジュール

フロアランプを、行燈風サウンド レベル メーターとして光らせるために、個別に制御できるRGB LEDのWS2812Bをを使います。

「〇〇番目のLEDさんは、△△色に光ってください〜」

という情報さえ流せば、その通りに光るので、多くのLEDを個別に光り方を変えるのに便利なものです。

1mあたりにWS2812Bが60m個並べられているカラーLEDテープを3mと、サウンドセンサーモジュール、そして可変抵抗(ボリューム)3個を入手しました。

以前に、可変抵抗1個を使ってナイトライダー風サウンド レベル メーターを作ったことがありますが、今回はフロアランプでもあるので、

  • 光量
  • 光らせ方

の3項目を可変抵抗で選択できるように進化させます。

表示イメージとパターン

 もともとは、「焚火 in 行燈」コンセプトで始めたプロジェクトですが、純粋にサウンド レベル メーター風のパターンも含めて、合計7種類の光らせ方を準備します。

 行燈なので、まずは白色で光らせます。

写真は紫色のように写っていますが、実際には白色です。

WS2812Bで行燈を改造。(白色)

 サウンドレベルのバーが立ちます。色は炎のように、オレンジのグラデーションです。

WS2812Bで行燈を改造。(サウンド レベル メーター 赤色)

 サウンドレベルのバーが立ちます。色は可変抵抗で選択した単色です。

WS2812Bで行燈を改造。(サウンド レベル メーター 好きな色)

 サウンドレベルのバーが立ちます。色は徐々に変化します。

WS2812Bで行燈を改造。(サウンド レベル メーター 色は徐々に変化)

 焚き火風に光らせます。色は、「オレンジ白色」の3色です。

WS2812Bで行燈を改造。(ファイヤー 赤色)

 焚き火風に光らせます。色は、「水色白色」の3色です。

WS2812Bで行燈を改造。(ファイヤー 青色)

 焚き火風に光らせます。色は、「可変抵抗で選択した色白色」の2色です。

WS2812Bで行燈を改造。(ファイヤー 好きな色)

 焚き火風に光らせます。色は、「徐々に変化する白色」の2色です。

WS2812Bで行燈を改造。(ファイヤー 徐々に変化する色)

回路図

Arduinoで自作するサウンド レベル メーターの回路図

Fritzingで回路図を作成しました。

マイクモジュールは、MAX4466を搭載したものを使っています。

3個の可変抵抗(ボリューム)は、

  • R1;光量
  • R2;表示7パターンの切り替え
  • R3;色

の調節(選択)をするために使います。

Arduinoを使って、LEDを炎のパターンで光らせる

Arduinoのアナログ入力には、それぞれの可変抵抗から電圧で情報が送られます。

  • 電圧; 0〜5V
  • Arduinoの読み取り値; 0〜1023(整数)

0〜1023をArduinoのスケッチ(プログラム)で選択肢に置き換えれば、LEDの発光を調節できます。

今回は180個が繋がっているものを使うWS2812Bは、先程も簡単に紹介しましたが、それぞれのLEDが個別に信号処理して、色や光量を認識して発光するので、信号線1本と電源の、合計3本の電線を繋ぐだけで、自在に光を制御できる優れものです。

WS2812Bで、炎のランプを!

スケッチは、記事の最後に掲載しておきますね。

炎が踊るフロアーランプ

LEDの配置

LEDを、ランプ中心部の柱にどう配置するか?について、少し試行錯誤しました。

  • 螺旋状に巻く?
  • 縦の列状に配置する?

結論から言うと、縦に配置した方が、LEDの貼り付け作業も、炎やサウンドレベルメーターのスケッチ(プログラム)の作成も楽です。

私は4列で配置することにしました。

WS2812B ファイヤー

180個のWS2812Bテープなので、

「45行x4列 」

になる筈ですが、試行錯誤の段階でちょっとした事故があり、数セルが使えなくなってしまったので、やむなく43x 4の配置になりました。

ウエルカム点灯

電源投入時の動作は、3種類を仕込んでおきます。

  1. ランダム点滅(白色)

    f:id:solocamptouring:20210626123315j:plain

  2. 白色光を上下に一回走らせる

    f:id:solocamptouring:20210626123321j:plain

  3. レインボー色の光を上下に一回走らせる

    f:id:solocamptouring:20210626123435j:plain

静止画だと、どれだけ綺麗に光っているか?が伝わらないですが、光り方はメイキング動画でも、ご確認いただけます。

 

youtu.be

スケッチ(プログラム)

スケッチは、色々なパターンの光らせ方が準備されている、GitHubのFastLEDライブラリをベースにさせていただきます。

炎(fire)パターンは、派生バージョンも含めていくつか紹介されており、Fire2012WithPaletteの、炎を作成する部分のスケッチを使用させていただき、4列の炎専用に少し手を加えました。

もっと短いスケッチにできると思いますが、メモリー容量に余裕があるので良い!ということにして、同じ命令文が何度もでてきます。

#include "FastLED.h"
#define DATA_PIN    5
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define HEIGHT 43
#define WIDTH 4
#define NUM_LEDS HEIGHT*WIDTH
#define MAX_BRIGHTNESS 90      // Full is 164
#define MIN_BRIGHTNESS 5       // 25% is better
#define lighting_PER_SECOND 30 // Mainly for fire frame pattern

CRGB leds[NUM_LEDS];
CRGBPalette16 gPal;

const int sampleWindow = 50; // Sound sample window width in mS
unsigned int sample;
int SoundCenterAdjust = 0; // max 1024/2
int SoundLevelAdjust = 6; // defo and min 1 In case bar is small with the usual sound
int BRIGHTNESS = 50;
int scaleVol = 230; // Scale the heat value from 0-255
int SPARKING = 130; //50-200 out of 255
int COOLING = 70; // 20-100: Less = taller flames.  More = shorter flames. 
int colorSelect;
int micPin = 2;
int blightPin = 3;
int selectPin = 4;
int colorPin = 5;
int val = 0;
int numLedsToLight = 0;
long numLedsToLightCal = 0;
bool welcomeLight = true;
bool gReverseDirection = false;


void setup() {
  delay(3000); //delay for recovery
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
}

// List of patterns to cycle through
typedef void (*SimplePatternList[])();

SimplePatternList gPatterns = { allwhite, soundBarFire, soundBarSelect, soundBarRotate, fireRed, fireBlue, fireSelect, fireRotate};

uint8_t gCurrentPatternNumber = 0;  // Index number of which pattern is current
uint8_t gHue = 96;                  // rotating "base color" used by many of the patterns 0to255
// End of the list of patterns to cycle through


void loop()
{
// Data collection
  // Choose the blightness
   if (analogRead(blightPin) < 50) {
    BRIGHTNESS = 0 ;
   } else {
    BRIGHTNESS = map(analogRead(blightPin), 50, 1024, MIN_BRIGHTNESS, MAX_BRIGHTNESS) ;
   } 
    FastLED.setBrightness(BRIGHTNESS); 

  // Choose the color
   if (analogRead(colorPin) < 50) {
    colorSelect = 0 ;
   } else if (analogRead(colorPin) < 950) {
    colorSelect = map(analogRead(colorPin), 50, 949, 0, 255) ;
   } else {
    colorSelect = 255;
   }

  // Choose the pattern
   val = analogRead(selectPin); //check the volume to choose the pattern
      if (val < 130) {
    gCurrentPatternNumber = 0;
       } else if (val < 260){
    gCurrentPatternNumber = 1;
       } else if (val < 390){
    gCurrentPatternNumber = 2;
       } else if (val < 520){
    gCurrentPatternNumber = 3;
       } else if (val < 650){
    gCurrentPatternNumber = 4;
       } else if (val < 780){
    gCurrentPatternNumber = 5;
       } else if (val < 910){
    gCurrentPatternNumber = 6;
       } else {
    gCurrentPatternNumber = 7;      
       }
    gPatterns[gCurrentPatternNumber](); // Call the current pattern function once, updating the 'leds' array

// End of data collection


// Welcome lighting
  if (welcomeLight == true) {
  // random speckles that blink in and fade smoothly
    for(int led = 0; led < 150; led++) { 
    fadeToBlackBy( leds, NUM_LEDS, 100);
    int pos = random16(NUM_LEDS);
    leds[pos] += CRGB::White;
    FastLED.show();  
    delay (100-led/2);
    }  
    
  // white up and down
    for(int led = 0; led < HEIGHT; led++) { 
    fadeToBlackBy( leds, NUM_LEDS, 100);
    leds[led] += CRGB::White ; 
    leds[HEIGHT*2-1-led] += CRGB::White ; 
    leds[HEIGHT*2+led] += CRGB::White ; 
    leds[HEIGHT*4-1-led] += CRGB::White ; 
    FastLED.show();    
    delay (80);
    }

    for(int led = 0; led < HEIGHT; led++) { 
    fadeToBlackBy( leds, NUM_LEDS, 100);
    leds[HEIGHT-1-led] += CRGB::White ; 
    leds[HEIGHT+led] += CRGB::White ; 
    leds[HEIGHT*3-1-led] += CRGB::White ; 
    leds[HEIGHT*3+led] += CRGB::White ; 
    FastLED.show();    
    delay (80);
    }
  
  // rainbow up and down
    for(int led = 0; led < HEIGHT; led++) { 
    fadeToBlackBy( leds, NUM_LEDS, 100);
    leds[led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    leds[HEIGHT*2-1-led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    leds[HEIGHT*2+led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    leds[HEIGHT*4-1-led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    FastLED.show();    
    delay (80);
     }
 
    for(int led = 0; led < HEIGHT; led++) { 
    fadeToBlackBy( leds, NUM_LEDS, 100);
    leds[HEIGHT-1-led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    leds[HEIGHT+led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    leds[HEIGHT*3-1-led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    leds[HEIGHT*3+led] = CHSV(255*led/HEIGHT, 255, 255) ; 
    FastLED.show();    
    delay (80);
    }    
    welcomeLight = false;  //End welcome lighting
  }else{

// Let's light them now
  random16_add_entropy( random());
  gPatterns[gCurrentPatternNumber]();
  FastLED.show();  // send the 'leds' array out to the actual LED strip
  FastLED.delay(1000 / lighting_PER_SECOND);
  EVERY_N_MILLISECONDS( 300 ) { gHue++; } // slowly cycle the color through the rainbow
// End of lighting
    }  
}


void fireRed() 
{
  gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White);
  static uint8_t heat[NUM_LEDS]; // Array of temperature readings

  // Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / HEIGHT) + 2)); //heat-randam8 with a floor of 0
    }
  
  // Heat from each cell drifts 'up' and diffuses a little
    for( int k= HEIGHT - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
      heat[2*HEIGHT-1-k] = (heat[2*HEIGHT-1-k + 1] + heat[2*HEIGHT-1-k + 2] + heat[2*HEIGHT-1-k + 2] ) / 3;
      heat[2*HEIGHT+k] = (heat[2*HEIGHT+k - 1] + heat[2*HEIGHT+k - 2] + heat[2*HEIGHT+k - 2] ) / 3;
      heat[4*HEIGHT-1-k] = (heat[4*HEIGHT-1-k + 1] + heat[4*HEIGHT-1-k + 2] + heat[4*HEIGHT-1-k + 2] ) / 3;
    }
    
    //  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int x = random8(19);
      int y ;
      if(x > 14){
        y = 4*HEIGHT+16-x;
      } else if(x > 9){
        y = 2*HEIGHT-9+x;
      } else if(x > 4){
        y = 2*HEIGHT+4-x;
      } else {
        y = x;
      }
      heat[y] = qadd8( heat[y], random8(160,255) ); //heat+randam8
    }

    // Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      uint8_t colorindex = scale8( heat[j], scaleVol);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      if( gReverseDirection ) {
        if(j < HEIGHT){
          pixelnumber = (HEIGHT-1) - j;
          }else if(j < 2*HEIGHT){
          pixelnumber = HEIGHT+j;
          }else if(j < 3*HEIGHT){
          pixelnumber = (3*HEIGHT-1) - j;
          }else{
          pixelnumber = 3*HEIGHT+j;
          }
      } else {
        pixelnumber = j;
      }
      leds[pixelnumber] = color;
    }
}

void fireRotate() 
{
  gPal = CRGBPalette16( CRGB::Black, CHSV( gHue, 255, 192), CRGB::White);
  static uint8_t heat[NUM_LEDS]; // Array of temperature readings

  // Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / HEIGHT) + 2)); //heat-randam8 with a floor of 0
    }
  
  // Heat from each cell drifts 'up' and diffuses a little
    for( int k= HEIGHT - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
      heat[2*HEIGHT-1-k] = (heat[2*HEIGHT-1-k + 1] + heat[2*HEIGHT-1-k + 2] + heat[2*HEIGHT-1-k + 2] ) / 3;
      heat[2*HEIGHT+k] = (heat[2*HEIGHT+k - 1] + heat[2*HEIGHT+k - 2] + heat[2*HEIGHT+k - 2] ) / 3;
      heat[4*HEIGHT-1-k] = (heat[4*HEIGHT-1-k + 1] + heat[4*HEIGHT-1-k + 2] + heat[4*HEIGHT-1-k + 2] ) / 3;
    }
    
    //  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int x = random8(19);
      int y ;
      if(x > 14){
        y = 4*HEIGHT+16-x;
      } else if(x > 9){
        y = 2*HEIGHT-9+x;
      } else if(x > 4){
        y = 2*HEIGHT+4-x;
      } else {
        y = x;
      }
      heat[y] = qadd8( heat[y], random8(160,255) ); //heat+randam8
    }

    // Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      uint8_t colorindex = scale8( heat[j], scaleVol);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      if( gReverseDirection ) {
        if(j < HEIGHT){
          pixelnumber = (HEIGHT-1) - j;
          }else if(j < 2*HEIGHT){
          pixelnumber = HEIGHT+j;
          }else if(j < 3*HEIGHT){
          pixelnumber = (3*HEIGHT-1) - j;
          }else{
          pixelnumber = 3*HEIGHT+j;
          }
      } else {
        pixelnumber = j;
      }
      leds[pixelnumber] = color;
    }
}

void fireBlue() 
{
  gPal = CRGBPalette16( CRGB::Black, CRGB::Blue, CRGB::Aqua,  CRGB::White);
  static uint8_t heat[NUM_LEDS]; // Array of temperature readings

  // Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / HEIGHT) + 2)); //heat-randam8 with a floor of 0
    }
  
  // Heat from each cell drifts 'up' and diffuses a little
    for( int k= HEIGHT - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
      heat[2*HEIGHT-1-k] = (heat[2*HEIGHT-1-k + 1] + heat[2*HEIGHT-1-k + 2] + heat[2*HEIGHT-1-k + 2] ) / 3;
      heat[2*HEIGHT+k] = (heat[2*HEIGHT+k - 1] + heat[2*HEIGHT+k - 2] + heat[2*HEIGHT+k - 2] ) / 3;
      heat[4*HEIGHT-1-k] = (heat[4*HEIGHT-1-k + 1] + heat[4*HEIGHT-1-k + 2] + heat[4*HEIGHT-1-k + 2] ) / 3;
    }
    
    //  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int x = random8(19);
      int y ;
      if(x > 14){
        y = 4*HEIGHT+16-x;
      } else if(x > 9){
        y = 2*HEIGHT-9+x;
      } else if(x > 4){
        y = 2*HEIGHT+4-x;
      } else {
        y = x;
      }
      heat[y] = qadd8( heat[y], random8(160,255) ); //heat+randam8
    }

    // Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      uint8_t colorindex = scale8( heat[j], scaleVol);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      if( gReverseDirection ) {
        if(j < HEIGHT){
          pixelnumber = (HEIGHT-1) - j;
          }else if(j < 2*HEIGHT){
          pixelnumber = HEIGHT+j;
          }else if(j < 3*HEIGHT){
          pixelnumber = (3*HEIGHT-1) - j;
          }else{
          pixelnumber = 3*HEIGHT+j;
          }
      } else {
        pixelnumber = j;
      }
      leds[pixelnumber] = color;
    }
}

void fireSelect() 
{
  gPal = CRGBPalette16( CRGB::Black, CHSV( colorSelect, 255, 192), CRGB::White);
  static uint8_t heat[NUM_LEDS]; // Array of temperature readings

  // Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((COOLING * 10) / HEIGHT) + 2)); //heat-randam8 with a floor of 0
    }
  
  // Heat from each cell drifts 'up' and diffuses a little
    for( int k= HEIGHT - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
      heat[2*HEIGHT-1-k] = (heat[2*HEIGHT-1-k + 1] + heat[2*HEIGHT-1-k + 2] + heat[2*HEIGHT-1-k + 2] ) / 3;
      heat[2*HEIGHT+k] = (heat[2*HEIGHT+k - 1] + heat[2*HEIGHT+k - 2] + heat[2*HEIGHT+k - 2] ) / 3;
      heat[4*HEIGHT-1-k] = (heat[4*HEIGHT-1-k + 1] + heat[4*HEIGHT-1-k + 2] + heat[4*HEIGHT-1-k + 2] ) / 3;
    }
    
    //  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < SPARKING ) {
      int x = random8(19);
      int y ;
      if(x > 14){
        y = 4*HEIGHT+16-x;
      } else if(x > 9){
        y = 2*HEIGHT-9+x;
      } else if(x > 4){
        y = 2*HEIGHT+4-x;
      } else {
        y = x;
      }
      heat[y] = qadd8( heat[y], random8(160,255) ); //heat+randam8
    }

    // Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      uint8_t colorindex = scale8( heat[j], scaleVol);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
       if( gReverseDirection ) {
        if(j < HEIGHT){
          pixelnumber = (HEIGHT-1) - j;
          }else if(j < 2*HEIGHT){
          pixelnumber = HEIGHT+j;
          }else if(j < 3*HEIGHT){
          pixelnumber = (3*HEIGHT-1) - j;
          }else{
          pixelnumber = 3*HEIGHT+j;
          }
      } else {
        pixelnumber = j;
      }
      leds[pixelnumber] = color;
    }
}

void soundBarFire() 
{

  // Sound level to lighting LED amount
   unsigned long startMillis= millis();  // Start of sample window
   unsigned int peakToPeak = 0;   // peak-to-peak level
   unsigned int signalMax = 0;
   unsigned int signalMin = 1024;
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(micPin);
      if (sample < 1024)
      {
         if (sample > signalMax)
         {
            signalMax = sample;  // save the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;  // save the min levels
         }
      }       
   }
   peakToPeak = signalMax - signalMin ;  // max - min = peak-peak amplitude
   numLedsToLightCal = ((peakToPeak-SoundCenterAdjust)*HEIGHT*SoundLevelAdjust)/1024 ;
   if(numLedsToLightCal >= HEIGHT){
    numLedsToLight = HEIGHT;
   }else {
   numLedsToLight = numLedsToLightCal; // adjusting to the output of the mic 
   }
    fadeToBlackBy( leds, NUM_LEDS, 200);

  for(int led = 0; led < numLedsToLight; led++) { 
  leds[led] += CHSV( 2*(numLedsToLight-1-led), 255, 192);
  }
  for(int led = HEIGHT*2-numLedsToLight; led < HEIGHT*2; led++) { 
  leds[led] += CHSV( 2*(led-(HEIGHT*2-numLedsToLight)), 255, 192);
  }
  for(int led = HEIGHT*2; led < HEIGHT*2+numLedsToLight; led++) { 
  leds[led] += CHSV( 2*(HEIGHT*2+numLedsToLight-1-led), 255, 192);
  }
  for(int led = HEIGHT*4-numLedsToLight; led < HEIGHT*4; led++) { 
  leds[led] += CHSV( 2*(led-(HEIGHT*4-numLedsToLight)), 255, 192);
  }
}

void soundBarSelect() 
{

  // Sound level to lighting LED amount
   unsigned long startMillis= millis();  // Start of sample window
   unsigned int peakToPeak = 0;   // peak-to-peak level
   unsigned int signalMax = 0;
   unsigned int signalMin = 1024;
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(micPin);
      if (sample < 1024)
      {
         if (sample > signalMax)
         {
            signalMax = sample;  // save the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;  // save the min levels
         }
      }       
   }
   peakToPeak = signalMax - signalMin ;  // max - min = peak-peak amplitude
   numLedsToLightCal = ((peakToPeak-SoundCenterAdjust)*HEIGHT*SoundLevelAdjust)/1024 ;
   if(numLedsToLightCal >= HEIGHT){
    numLedsToLight = HEIGHT;
   }else {
   numLedsToLight = numLedsToLightCal; // adjusting to the output of the mic 
   }
    fadeToBlackBy( leds, NUM_LEDS, 200);


  for(int led = 0; led < numLedsToLight; led++) { 
  leds[led] += CHSV( colorSelect, 255, 192);
  }
  for(int led = HEIGHT*2-numLedsToLight; led < HEIGHT*2; led++) { 
  leds[led] += CHSV( colorSelect, 255, 192);
  }
  for(int led = HEIGHT*2; led < HEIGHT*2+numLedsToLight; led++) { 
  leds[led] += CHSV( colorSelect, 255, 192);
  }
  for(int led = HEIGHT*4-numLedsToLight; led < HEIGHT*4; led++) { 
  leds[led] += CHSV( colorSelect, 255, 192);
  }
}

void soundBarRotate() 
{

  // Sound level to lighting LED amount
   unsigned long startMillis= millis();  // Start of sample window
   unsigned int peakToPeak = 0;   // peak-to-peak level
   unsigned int signalMax = 0;
   unsigned int signalMin = 1024;
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(micPin);
      if (sample < 1024)
      {
         if (sample > signalMax)
         {
            signalMax = sample;  // save the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;  // save the min levels
         }
      }       
   }
   peakToPeak = signalMax - signalMin ;  // max - min = peak-peak amplitude
   numLedsToLightCal = ((peakToPeak-SoundCenterAdjust)*HEIGHT*SoundLevelAdjust)/1024 ;
   if(numLedsToLightCal >= HEIGHT){
    numLedsToLight = HEIGHT;
   }else {
   numLedsToLight = numLedsToLightCal; // adjusting to the output of the mic 
   }
    fadeToBlackBy( leds, NUM_LEDS, 200);

   for(int led = 0; led < numLedsToLight; led++) { 
   leds[led] += CHSV( gHue, 255, 192); 
   }
  for(int led = HEIGHT*2-numLedsToLight; led < HEIGHT*2; led++)  { 
   leds[led] += CHSV( gHue, 255, 192); 
  }
  for(int led = HEIGHT*2; led < HEIGHT*2+numLedsToLight; led++) { 
   leds[led] += CHSV( gHue, 255, 192); 
  }
  for(int led = HEIGHT*4-numLedsToLight; led < HEIGHT*4; led++) { 
   leds[led] += CHSV( gHue, 255, 192); 
  } 
}

void allwhite() 
{
  // all led white color
  fill_solid(leds, NUM_LEDS, CRGB::White); 
}

 

今回は、フロアランプに焚火を仕込みましょう!というコンセプトで、お家ですごす時間を楽しくしてみました。

毎週末を使って、製作期間は一か月ほどかかりましたが、初めて見る人には「フロアランプが燃えている!」と思わせそうな完成度で、満足しています。

WS2812Bでファイヤーパターンのフロアライト

室内で焚火はできないアパート暮らしですが、お部屋に焚き火風の光があると、本当にキャンプしているような気分になって、うっとりと見入っています。

ラジコンを超低速で動かす!ラジコン自動走行への第2歩目

もう1年以上前になってしまいましたが、トイラジコンに超音波センサーを5個搭載して自動走行に挑戦していました。

しかし、高回転型のDCモーターを使っていたため、スピードが早すぎて距離の計測が追いつかない!という問題がありました。

対策を考えながらも、しばらく放置していましたが、挑戦再開の一歩を踏み出します。

まずは、スピードを落としつつ正確に移動距離を制御ために、ステップモーターに変更してみます。

Arduinoとステップモーター28BYJ-48で、ラジコンを自動走行させてみます

前回作った自動走行トイラジコン

f:id:solocamptouring:20190105132806j:plain

以前作った自動走行ロボットの動き方を、簡単に紹介しておきます。

  • 前側3方向(0°と±約45°)を測距しながら、障害物との距離に合わせたスピードで、障害物から遠い方向に軽く操舵しながら前進します。
  • 前方が行き詰まったら、後方2方向(±約45°)を測距しながら、スペースが開いている方に少し後退して、その場で回転します。
  • 回転後は、また前方3方向を測距しながら前進します。

動力部は、トイラジコンに付属していたDCモーターを使って、PWMで速度を変えています。

<PWM>

細かくON/OFFを繰り返す波形で、電圧を落とす(変える)のと同じような効果を得るためのものです。

そのON時間の割合がデューティー比です。

改造していく途中でわかってきたのですが、デューティー比が低くなる低速では、モーターのトルクも細くなります。

そのため、狙っていたような極低速では、トイラジコンが動かなくなってしまいました。

仕方なく、トイラジコンが動くぎりぎりのデューティー比のところを最低速として動かしましたが、それでもスピードが早すぎて測距が追いつかない場合があります。

その結果、障害物に当たってから後退し始めたり、180°転回を繰り返したりと、暴れん坊の自動走行になっていました。

ステップモーターで自動走行に再挑戦します

前回作った自動走行ロボットを改良して、低速域でのスピードと移動距離を、しっかり制御できるようにします。

5個の測距センサーは、前回取り付けたものを、今後もそのまま使う予定です。

ステップモーターを動かすためには、直流モーターの速度制限(PWM)とは違ったタイプのドライバーが必要になるので、制御用のハードも変更が必要となります。

今回は駆動モーターの変更に注力して作業します。

ステップモーター(ステッピングモーター)を準備します

トイラジコンのモジュール化されているギヤボックスへの改造を最小限にしたいので、トイラジコンに付属しているDCモーターとほぼ同じ大きさのステップモーターを探してみます。

トイラジコンのDCモーターの外形は、

  • モーター外径:約24mm
  • モーター長さ:約30mm

です。

f:id:solocamptouring:20190105135855j:plain

NMB PM25Lを試してみる

トイラジコンのDCモーターとまったく同じ大きさのステップモーターは探しきれなかったので、外径の大きさが近いNMB PM25Lを選択しました。

  • モーター外径:25mm
  • モーター長さ:16mm
  • シャフト径:2mm

f:id:solocamptouring:20190105140032j:plain

モーターシャフトに取り付けるギヤは、2mmのシャフト径用のものが必要です。

トイラジコンのDCモーターについていたのは9歯のメタルギヤでしたが、ステップモーター用で8歯の樹脂ギヤを入手しました。

ステップモータードライバーは、4フェーズ5線式ユニポーラ用の既製品ボードを使います。

駆動ユニットにステップモーターNMB PM25Lを組みます

トイラジコンのDCモーターと減速ギヤは、樹脂のハウジングで一体化されています。

入手したステップモーターの外形は少し大きめなので、ハウジングのモーター格納部は、躊躇せずに切断します。

f:id:solocamptouring:20190105140744j:plain

上の写真は右側の部品だけ加工した状態です。

左側の部品もバッサリと切断して、ステップモーターをつけてみました。

f:id:solocamptouring:20190105141323j:plain

NMB PM25Lは6.4Vでラジコンを動かせるのか?

ここまで深く考えずに作業してきましたが、NMB PM25Lはユニポーラ12V駆動です。

トイラジコンの減速ギヤをそのまま使っているので、6.4Vでも動くのではないかと期待していました。

結果はあっけなく撃沈で、トイラジコンを動かせるほどのトルクは出せませんでした。

やはり電圧に合ったモーターを選ばないといけないと反省して、次の一手を考えます。

28BYJ-48を使ってみる

f:id:solocamptouring:20190105150058j:plain

駆動ユニットにステップモーター28BYJ-48を組みます

いつも使っているステップモーター28BYJ-48に、今回も活躍してもらうことにします。

減速ギヤ一体式のモーターなので、5~6.4Vでラジコンを動かせることは間違いありません。

ただし、トイラジコンのギヤボックスでさらに減速するとなると、超超低速走行になってしまい、動いていても停止して見えそうです。

そこで、トイラジコンの2段減速ギヤのうち最終段だけを使うことにして、一段目のギヤを直接ステップモーターで回します。

ギヤボックスは、またまた躊躇せずに切断と穴開けを敢行します。

ギヤにも28BYJ-48にちょうどいい形と大きさの穴をあけたら、動力部を組みあげてみました。

トイラジコンのモーターを28BYJ-48にのせかえる

ステップモーター28BYJ-48を6.4Vで動かしてみます

トイラジコンのギヤボックスとホイールを組んだ状態で、ステップモーターを動かしてみます。

www.youtube.com

ものすごく低速ですが、これこそ目指していた回転速度です。

しっかりと壁などを検知させたいので、月面探査機のような速度で充分です。

ステップモーター28BYJ-48と動力部を車体に組んでみました。

Arduinoとステップモーターでラジコンを自動走行させてみます

スペースを確認しながらステップモーターの取り付け角を決めたので、トイラジコンにぴったりと収まっていますね!

今後の予定(予告)

今回はステップモーターを2個使っているので、パルスを発生させるものが合計3個必要になります。

  • 超音波センサー
  • 右ステップモーター
  • 左ステップモーター

超音波センサーは、発音と測距の間に不定でバラツキ幅の大きいタイムラグが発生します。

そのため、超音波センサーを動かしているArduinoは、測距と同時にステップモーター用のパルスを発生させることができません。

このため、まず最初に、Arduinoを複数使うか、モータードライバーを追加するかを決める予定です。

Toy RC with Arduino and 28BYJ-48

Arduinoでの速度と方向制御(構想)

前回の自動走行化では、トイラジコン付属のDCモーターをPWM制御で速度コントロールしていましたが、今回はステップモーターに数種類のパルス信号の組み合わせを出力して、目的の速度と動きを作る予定です。

右左折の操舵も、簡単なプログラムで実現したいので、曲がる時の速度は、

  • 外輪:内輪 = 2:1

とします。

走行パターンも、基本的には

  1. 直進もしくは後退
  2. 右もしくは左への操舵
  3. 右もしくは左への180°転回

の3種類だけで、前後進(1)と操舵(2)は組み合わせで使います。

前進時は3個、後退時は2個のセンサーが測距した障害物の方向と距離に応じて、3種類の走行パターンを切り替えていき、自動走行化します。

To be continued!

Arduinoとモータードライバーの構成を決めたら、後は回路を組んで、スケッチ(プログラム)を少し変えるだけですが、その結果はまた次回とさせていただきます!

きっと、極低速力強く動くだけでなく、作動音も静かになって帰ってきます。

高性能なセンサーを使わずに、超音波センサー5個だけで実現するトイロボットの自動走行化、時期は未定ですが次回のお披露目まで、しばらくお待ちください!

色と明るさを変えられる集魚灯。ArduinoとLEDボードで自作したら爆釣モードに入った!

先日、初めての夜釣りで小さい根魚を釣り上げました。

f:id:solocamptouring:20190427081000j:plain

手のひらに収まる、あまりにも可愛いサイズだったので、海に元気にお帰りになりました。

次回は、なんとしてでも大きな根魚に出会いたいので、魚が集まりやすい集魚灯を自作して、夜釣りに導入したいと思います。

f:id:solocamptouring:20190427134321j:plain

魚が集まる集魚灯の色

集魚灯に魚が集まってくる理由を調べてみました。

陸地からのチョイ投げタイプの海釣りで使う集魚灯は、発光性プランクトンや夜光虫を模したものです。

集魚灯の光に、プランクトンや夜光虫を食べる虫や小魚が寄ってきて、さらにそれらを捕食する魚も寄ってくるようです。食物連鎖でなんでも釣れそうですね!

発光性プランクトンといえば青色緑色のイメージです。

しかし、疑似餌(ルアー)が、実際にはありえない配色のものがよく釣れたりするので、集魚灯に最適な色も、実際に試してみないとわかりません。

そこで、自由に色を変えられる集魚灯を作って、その時々の最適な色を探ってみたいと思います。

集魚灯の自作

なるべく簡単に、そして安く集魚灯を作ってみます。

魚を集める機能を追い求めるので、見栄えは二の次です!

集魚灯の部品を準備します

Arduino

コンパクトな大きさで使いやすいArduino Nano(互換品)を使います。

写真は、今回使ったDC5Vの外部電源入力用の部品も写っています。

f:id:solocamptouring:20190427091835j:plain
f:id:solocamptouring:20190427104755j:plain

Arduinoのスケッチ(プログラム)は、記事の最後に掲載しています。

WS2812Bボード

集魚灯の発光部は、WS2812B を7個 配置した円形ボードを使います。

直径23mmと小型なので、堤防で根魚を狙う時などの集魚灯には、ちょうどいい性能と大きさです。

自作した集魚灯に使うWS2812B

ミサイルスイッチ

スイッチはなくても集魚灯は動かせるのですが、実際に使う時の雰囲気づくりのために、他の作品でもミサイルスイッチを使っています。

プロジェクトボックスのスペースをとってしまいますが、ミサイルスイッチで電源を入れる儀式をしてから使うと、何かが起きそうで気分が盛り上がります。

今回は、青色のミサイルスイッチで、釣れるスイッチのおまじないをします。

集魚灯の電源はミサイルスイッチで
集魚灯のプロジェクトボックス

可変抵抗(ボリューム)

集魚灯をコントロールするために、可変抵抗を使います

  • 光の色
  • 光の強さ

をそれぞれ選択できるように、2個の10KΩ可変抵抗を準備します。

コネクターとコード

海中に入れる発光部は5mのコードでコントローラに繋ぎます。

持ち運びやすくしたいので、AWG28の細い電線が3本入っているコードを準備しました。

コントロールボックスと発光部をつなぐコードは、コネクターで接続します。

Arduinoの自作作品に似合うコネクター

通販サイトでは「航空コネクタ」で検索するとみつかる、この少し仰々しいコネクターも、雰囲気づくりのために愛用しています。

集魚灯の回路をつくる

 WS2812Bボードは、電源線2本と信号線1本の合計の3本の電線をつなぐだけで、様々な光らせ方ができる優れものです。回路も単純な構成になるので、自作品には便利に使えます。

回路図は、Fritzingで作成しました。

f:id:solocamptouring:20190427101733j:plain

スペースの都合で、回路図にWS2812Bは5個だけ表現されていますが、実際には7個が直列につながっています。

発光部を除いて、配線をはんだ付けしてみました。

自作集魚灯の回路

今回は配線が少ないので、Arduinoには直接はんだ付けしています。

はんだごての熱でArduino上の部品を壊さないように、注意しながら作業を進めました。

プロジェクトボックスに回路を入れる

コントローラを仕上げるために、プロジェクトボックスに各部品の取付穴をあけます。

コネクターだけは、ボックスに外側から固定する形なので、配線をはんだ付けする前にボックスにねじ止めします。

続いて、スイッチと可変抵抗も、ボックスの上面に内側から取り付けます。

Arduino Nanoは、ボックス内に両面テープで貼り付けておきました。

自作集魚灯のコントローラ
自作集魚灯のコントローラの中

蓋を閉じたら、コントロールボックスの完成です。

試しに光らせてみましょう

5mの3芯コードの両端に、WS2812Bボードとコネクタをつけて、実際に光らせてみました。

自作集魚灯がほぼ完成

集魚灯として本命案の青色や緑色は、一種類だけでなく、可変抵抗のツマミをまわすことで多彩な色をつくりだすことができます。

集魚灯としての効果は無いかもしれませんが、 ツマミをまわして赤色や黄色などの色も作れます。

記事の最後で紹介しているスケッチでは、色を調整する可変抵抗のツマミを右にいっぱいまで回すと、ゆっくりと全ての色に変わっていくようにしています。

発光部の防水化

WS2812Bボードは、水深5mの水圧でも耐えられるように、透明樹脂で覆って防水化します。 

透明樹脂を流し込むケースに何を使おうか考えていたら、卓球の玉に目がとまりました。

大きさが発光部にちょうどよく、乳白色なので周囲に光を拡散してくれそうです。

さっそく玉の上部を切開します。

錘をつける針金を発光部の穴に通して玉の中にいれた後に、クリスタルレジン を流し込みました。

そのまま動かさないようにして樹脂が固まったら完成ですが、待ちきれないなので再度光らせてみます。

玉の下部に無造作に貼られているマスキングテープは、クリスタルレジンが流れ出ないように塞いでいるもので、樹脂が固まったら取り除きます。

自作集魚灯の発光部

狙いどおり、乳白色の卓球の玉全体で、いい感じで光っています。

自作集魚灯のシェイクダウン

後日、ためしに使ってみました。

釣りには不向きな強風の日に、漁港の堤防の風裏になる場所で、昼過ぎから釣り竿をたらしつつ、夜を待ちます。

明るいうちは何も釣れないままで時間が過ぎていき、あたりが暗くなったところで集魚灯の電源を入れて、釣りをしている場所から2mほど離れた海中に投入してみました。

すると、魚が活発に動く時間になったのもあると思いますが、種類は同じで前回よりも(成長したので!?)大きめの魚が、ほぼ入れ食いとなりました!

自作集魚灯に群がる根魚
色を変えて海中で淡く光る自作集魚灯

キャッチ&リリースを繰り返しましたが、あまりにも簡単に釣れるので、何匹釣ったか数えるのは途中でやめてしまいました。これは、もう夜釣りでは手放せません!

効果的な光の色は、少し青色が入った緑色が良さそうですが、これはもう少し継続的に試してみたいと思います。

集魚灯のスケッチ(プログラム)

GitHubのFastLEDライブラリを使って、簡単な命令文で光らせているので、短いスケッチになっています。

#include "FastLED.h"
#define DATA_PIN    4
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define HEIGHT 1
#define WIDTH 7
#define NUM_LEDS HEIGHT*WIDTH

CRGB leds[NUM_LEDS];

const int sampleWindow = 20; // Sample window width in mS
unsigned int sample;
int colorSelect;
int colorPin = 2;
int selectPin = 5;
int val = 0;
int numLedsToLight = 0;
int numLedsSide = 0;
int volumeLight = 0;

void setup() {

  delay(3000); //delay for recovery

  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
}

uint8_t gHue = 96; // rotating "base color" used by many of the patterns

void loop()
{
  colorSelect = analogRead(colorPin); //check the volume to choose the color

  if (analogRead(colorPin) < 50) {
    colorSelect = 0 ;
  } else if (analogRead(colorPin) < 950) {
    colorSelect = map(analogRead(colorPin), 50, 949, 0, 255) ;
  } else {
    colorSelect = gHue;
  }

  val = analogRead(selectPin); //check the volume to choose the volume

  if (val < 205) {
    volumeLight = 3;
  } else if (val < 1000) {
    volumeLight = map(analogRead(selectPin), 205, 1000, 3, 50);
  } else {
    volumeLight = 50;
  }

  FastLED.setBrightness(volumeLight);

  fill_solid(leds, NUM_LEDS, CHSV( colorSelect, 255, 192));

  FastLED.show();  // send the 'leds' array out to the actual LED strip

  EVERY_N_MILLISECONDS( 300 ) {
    gHue++;  // slowly cycle the "base color" through the rainbow
  }
}

LEDイルミネーションで、テントまわりの光をカスタマイズ!市販品の流用と、Arduinoでの自作品

キャンプ場で、水場などへの通り道付近にテントを設置する場合、夜間に自分や通行人が、ペグやガイロープに引っかかることがないか気になります。

光る目印でペグやガイロープの存在をアピールして、安全にキャンプを楽しむ方法について、前半では市販品の応用方法、後半は自作した内容を、それぞれまとめてみました。

キャンプでのイルミネーション

月明りをも切り裂くような強力な光を発するライトが多いので、周りのキャンパーさんに迷惑をかけないために、ライト類を点灯せずに洗い場などに行くことがあります。

同じことを考えてみえるキャンパーさんも多く、また自然を楽しまれているお子様の安全のためにも、自分のサイトのペグやガイロープの存在を光でアピールしたいと思います。

また、暗いなかで洗い場などから帰ってくる時に、自分のテントを発見しやすくするためにも、個性的なイルミネーションは効果的です。

しかし、あまりにも激しく主張するような光では、周りの雰囲気に合わない場合もあるので、できればホタルのような淡い光が欲しいところです。

簡単に入手できそうなものから、これらの条件にあう、キャンプに使えるイルミネーションを探してみた結果と、電子工作の経験がある場合の簡単な自作方法を紹介いたします。

手軽に入手できるものを使う

 キャンプ場でよく見かける物から、「これは使えそう!」という物まで、入手してすぐに、ペグやガイロープの目印として使えるものを選んでみました。

蛍光ブレスレット

f:id:solocamptouring:20181122052431j:plain

テーマパークやイベントでもお馴染みの、ポキッと折ると光り始めるライトスティックです。

使い捨てですが、一本が約20円と経済的で、夕暮れから翌朝まで、ちょうどいい明るさで光り続けます。

ブレスレット風に円形にできるのも、ペグやガイロープやポールに設置する際の自由度が増すメリットとなります。

光の強さを試してみるために、林間フリーサイトのキャンプ場で、蛍光グリーン色のものをテント周りの地面に4個配置してみました(上の写真)。

林間サイトでは自分のテントが闇に紛れこんでしまうことが多いですが、約20m離れたところからでも、やんわりとした蛍光色で自分のテントの位置が確認できて、真っ直ぐに帰ってこられます。

かといって、光が強すぎないので周りの雰囲気を損ねることもありません。

ソロキャンパーさんが多いような、月明かりの静寂を楽しむキャンプ場で使いたいマーカーです

アイスライト

本来は、カクテルグラスに氷のように入れて、イルミネーションを楽しむものです。

飲料に入れるものなので、しっかり防水されていてキャンプで使うのにも適しています。

蛍光ブレスレットよりは強めの光ですが、直視できないほど眩しいものではなく、さりげなくペグやガイロープの存在をアピールできます。

ただし、水に浸すと光りはじめるセンサー式になっているので、キャンプで使用する時には、実際に水に浸すか、センサー部にアルミホイルを使うなどで工夫することが必要です。

リモコン式のイルミネーション

アイスライトの機能に加えて、自分好みの光り方にリモコンで操作できる防水イルミネーションです。

リモコンから受信できる距離は最大で10mなので、一般的なファミリーキャンプ用テントでは、操作可能な範囲内に設置できます。

ON/OFF操作もリモコンでできるので、日中に設置しておいて、周りが暗くなったら簡単に点灯することも可能です。

ちょっとしたDIYでストラップなどを接着しておけば、ポールにぶら下げたりして、設置のバリエーションも広がります。

LED10灯が一体になっているものと、1灯づつ防水ケースに入っていて合計10個がセットになっているものがありますが、テントまわりに使う場合は、バラバラで10個になっているものを選びたいです。

太陽光発電のイルミネーション

昼間に太陽光で充電しておく前提ですが、電池交換が不要のLEDイルミネーションが使えます。

主にクリスマスのイルミネーションや庭先などで使われているもので、電線で繋がったものと、チューブ状のものがありますが、キャンプで使う場合は、絡まりにくいチューブ状の方が使い勝手が良さそうです。

光り方のパターンが変えられるものを選べば、フェードや点滅などの光も楽しめます。

テント周りで使うとなると約10m以上が必要になりますが、その長さだとLEDが100個程度使われているものが多いです。

さりげなく使うには光源が多めなので、ソロキャンパーさんが多い所よりも、ファミリーやグループキャンプなどで賑わうキャンプ場に向いています。

キャンプに似合うイルミネーションを自作します

キャンプに似合うイルミネーションを自作します

これまで紹介してきた既製品は、お手頃価格で入手できて光のバリエーションも豊富なので、手軽にテント周りに使いたい方に、おすすめです。

ここからは、なんでも自分で作ってみたくなる自作派の方への紹介です。

私の場合、使い捨て電池はなるべく使いたくありません。

思いたって突然キャンプに行く事が多いので、ソーラー充電方式では、準備不足により充電時間がしっかりと取れなさそうです。

モバイルバッテリーで一晩駆動できて、気分に応じて好みの光り方を選べられるようなイルミネーションを自作してみます。

Arduino とWS2812Bで、イルミネーションを自作する

Arduino とWS2812Bで、イルミネーションを自作する

光り方は、なるべく派手にならずに、しかしペグやガイロープにつまづいてしまう前に注意を引きたいので、マイコン搭載のLEDボードWS2812Bを10個だけ使います。

参考で、WS2812Bが5個の場合の回路図を、Fritzing で作成しました。

テントに使うクリスマス風イルミネーションをArduinoとWS2812Bで自作する

回路図のとおり、WS2812Bを使えば3本の電線だけで、数多くのLEDを自在に光らせることができます。

実際にはWS2812Bを10個を、0.2sq3芯の電線で約1m間隔で直列につないで、合計約10mのイルミネーションコードを作ります。

WS2812Bを10個つないでArduinoで光らせてみた

2個の可変抵抗(ボリューム)は、それぞれ色と光り方をコントロールするために使います。

光り方をコントロールするArduinoはプロジェクトボックスに入れて、モバイルバッテリーを電源にして光らせます。

Arduino とWS2812Bで、好きな光り方のイルミネーションを自作する

最後の仕上げで、電線をつないだWS2812Bのまわりを防水処理します。

当初は、クリスタルレジンと一緒に型に入れて固めようと考えていました。

しかし、狭い角度で発光するLEDの光を適度に拡散させるには、半透明のほうが良さそうです。

最終的に、100円ショップで入手したイルミネーションからシェードボールだけ使うことにしました。

f:id:solocamptouring:20181215174002j:plain
f:id:solocamptouring:20181215173953j:plain

シェードボールにLEDを入れたら、ホットメルトで穴をふさいでおきます。

思いつきに近い仕上げですが、光が拡散されて良い感じで光っています。

youtu.be

ご参考;クリスマス風イルミネーションのサンプルスケッチ

最後に、Arduinoを使って自作したイルミネーションの、スケッチを紹介しておきます。

光り方は、

  1. sinelon;流れ星のような光り方で往復
  2. confetti;ランダムなホタルのような光り方
  3. sinelonWithGlitter;1と2を合わせたもの
  4. allligh;10個全て点灯

の4種類から、5番のアナログピンにつないだ可変抵抗で選択します。

2番のアナログピンにつないだ可変抵抗では、好きな色か、徐々に変わる色かを選択します。

自作LEDイルミネーションで、テントまわりを安全に光らせてみます

スケッチは、GitHubのFastLEDライブラリを使いました。

#include "FastLED.h"
#define DATA_PIN    4
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define HEIGHT 1
#define WIDTH 10
#define NUM_LEDS HEIGHT*WIDTH
CRGB leds[NUM_LEDS];
#define BRIGHTNESS  3

const int sampleWindow = 20; // Sample window width in mS
unsigned int sample;
int colorSelect;
int colorPin = 2;
int selectPin = 5;
int val = 0;
int numLedsToLight = 0;
int numLedsSide = 0;

void setup() {
  delay(3000); //delay for recovery
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
}

// List of patterns to cycle through.  Each is defined as a separate function below.
typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { sinelon, confetti, sinelonWithGlitter, alllight };

uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 96; // rotating "base color" used by many of the patterns

void loop()
{

  colorSelect = analogRead(colorPin); //check the volume to choose the color
  if (analogRead(colorPin) < 50) {
    colorSelect = 0 ;
  } else if (analogRead(colorPin) < 950) {
    colorSelect = map(analogRead(colorPin), 50, 949, 0, 255) ;
  } else {
    colorSelect = gHue;
  }

  val = analogRead(selectPin); //check the volume to choose the pattern
  if (val < 205) {
    gCurrentPatternNumber = 0;
  } else if (val < 410) {
    gCurrentPatternNumber = 1;
  } else if (val < 615) {
    gCurrentPatternNumber = 2;
  } else if (val < 820) {
    gCurrentPatternNumber = 3;
  } else {
    gCurrentPatternNumber = 0;
  }

  gPatterns[gCurrentPatternNumber](); // Call the current pattern function once, updating the 'leds' array
  FastLED.show();  // send the 'leds' array out to the actual LED strip

  EVERY_N_MILLISECONDS( 300 ) {
    gHue++;  // slowly cycle the "base color" through the rainbow
  }
}

void sinelon()
{
  fadeToBlackBy( leds, NUM_LEDS, 8);
  int pos = beatsin16(13, 0, 9);
  leds[pos] += CHSV( colorSelect, 255, 192);
}


void confetti()
{
  // random colored speckles that blink in and fade smoothly
  fadeToBlackBy( leds, NUM_LEDS, 2);
  int pos = random16(600);
  leds[pos] += CHSV( colorSelect, 255, 192);
}

void sinelonWithGlitter()
{
  // built-in sinelon, plus some random sparkly glitter
  sinelon();
  if ( random8() < 3) {
    leds[ random16(NUM_LEDS) ] += CRGB::White;
  }
}


void alllight()
{
  // all led with color
  fill_solid(leds, NUM_LEDS, CHSV( colorSelect, 255, 192));
}

 

いつでもどこでも美味しく炊ける炊飯(炊爨)器をつくる。サーボモーターとArduinoで自動化

キャンプで炊飯(炊爨)する場合、経過時間に応じた火力調節が大事です。

沸騰状況やフタの動き量を目安に火力を調節していますが、自分好みの炊き上がりにできるか毎回不安になります。

自動で火力調整できれば、成功事例を忠実に再現できて安心できる上に、炊飯中の時間は他の作業に専念できると思い、サーボモーターとArduinoを使って、キャンプ用自動炊飯器を自作してみました。

キャンプで何度でも美味しくお米が炊ける半自動炊飯(炊爨)器の自作

飯盒炊飯の火力管理

飯盒炊飯の火力管理については、好みの炊き上がりに個人差があるため、いろんな意見があります。

お焦げは最少でいい私は、熱伝導率が良すぎて火力調節が難しいアルミの丸型飯盒を使って、

  1. 中火で2分
  2. 弱火で17分
  3. 強火で1分

として、自動化に挑戦してみます。

お米の量がいつもと違う場合は、時間管理は変えずに、火力をお米の量に比例して調整します。

自動炊飯器を自作します

自動炊飯器といっても、家で使うような釜一体の大掛かりなものは作りません。

キャンプ用の飯盒とCB缶バーナーを使いつつ、バーナーのガスコックに接続する自動火力調節器を自作して、トータルシステムとしては自動炊飯できるようにします。

部品の準備

駆動部は、サーボモーターを使います。

充分なトルクがある、下の写真左側のラジコンカー用のもので動かします。

f:id:solocamptouring:20181007050128j:plain

炊飯を開始する前に、可変抵抗(ボリューム)を操作してサーボモーターを動かし、バーナー火力の初期設定をします。

20分の炊飯時間は、炊飯開始ボタンを押した時からカウントダウンを開始します。

さらに、経過時間や火力調節状態が一目でわかるように、16x2文字のLCDモニターにリアルタイムで情報を表示します。

f:id:solocamptouring:20181007054656j:plain

主な使用部品は

  • Arduino
  • サーボモーター
  • スイッチ
  • ボリューム 
  • LCDモニター

です。

Arduinoでサーボモーターを動かす制御仕様

ArduinoとLCDモニターの電力は、モバイルバッテリーから供給します。

 それとは別に、サーボモーター用の電源として6.4Vバッテリーを準備します。

スイッチは、電源のオンオフではなく、初期入力の切り替えと炊飯時間の管理のために使います。

使い方の流れとしては、

  1. 電源コードをモバイルバッテリーにつなぐ。サーボモーターの電源はラジコン用6.4Vバッテリーにつなぐ。

    automatic rice cooker for camping

  2. CB缶残量や気圧などに応じた初期設定のために、ボリュームで火力を調節して強火にする

    f:id:solocamptouring:20181020200103j:plain

  3. 初期入力値切り替えボタンを押す
  4. ボリュームで火力を調節して弱火にする

    f:id:solocamptouring:20181020200128j:plain

  5. 炊飯スタートボタンを押

の順番に作業すると炊飯タイマーがカウントダウンを始めて、後は炊き上がりまで自動で火力調節します。

自動炊飯中に火力を微調整したい場合は、その都度ボリュームで火力調節すれば、その最新の値を初期設定の値に入れ替えて、その後も自動炊飯し続けます。

回路図

Fritzingで回路図をつくりました。

f:id:solocamptouring:20181020200958j:plain

回路図左上のサーボモーターは、その下のボリュームに連動して動かします。

初期設定切り替えボタン(S3)は、強火と弱火の初期設定を切り替えるために使用します。

炊飯スタートボタン(S2)を押したら炊飯タイマーがスタートして、自動で中火→弱火→強火の火力調節を始めます。

回路図右のLCDには、タイマー残り時間や火力を表示します。

はんだ付けで回路を仕上げて作動確認しました。

(下の写真のサーボモーターは、確認時の電源電力の事情により、小型のものを使っています)

Arduinoとサーボモーターでキャンプ用自動炊飯器を自作

完成!

木の風合いのボックスで仕上げたかったので、Arduinoと回路やモーターなどは、100円ショップの木の箱を加工して取り付けていきます。

バーナーの火力調節コックとサーボモーターとの連結は、事務用クリップを使います。

f:id:solocamptouring:20181007191940j:plain
f:id:solocamptouring:20181007194710j:plain

LCDモニターとボリュームなども取り付けて、完成しました。

キャンプ用自動炊飯器を試してみます

自動で上手に炊けるか、さっそく試してみました。

炊飯の準備

電力は2系統です。

サーボーモーターはラジコン用の6.4V、その他のArduinoなどにはジャンプスターターにも使えるモバイルバッテリーから供給します。

バーナーは愛用のSOTO Gストーブを使い、丸形飯盒で炊飯します。

いつでもキャンプで美味しく炊ける半自動炊飯(炊爨)器の自作。サーボモーターとArduino

白米ではなく、よくキャンプで作る炊き込みご飯を炊飯してみます。

ちょうどムカゴを入手できたので、一緒に飯盒へ入れておきます。

f:id:solocamptouring:20181021050545j:plain

自動炊飯してみます

Gストーブに火をつけて、強火と弱火の初期設定をした後に、炊飯スタートボタンを押します。

自動炊飯器にお任せにする火力調節は、思惑どおりにうまくできています。

youtu.be

今までの手動の炊飯では、途中で不安になって細かく火力調節していましたが、思い切って手を出さないようにします。

20分後、タイマーが終了して、サーボモーターがガスコックを閉じました。

そのまま、さらに20分ほど蒸らしてフタを開けてみたところ、私の好みの炊き具合にできていました!

気温の変化によっては、火力や時間を少し変える必要があるかもしれませんが、当分の間は何度でも同じ炊き上がりが望めそうです。

一年を通して使ってみて、外気温と火力や炊飯時間との関係性を見出せたら、温度センサーを追加して、自動化を進化させてもいいですね。

参考;サーボモーターを実用するスケッチ(プログラム)

サーボモーターを簡単に制御できるGitHubのVarSpeedServoライブラリを使ってスケッチを作りました。

参考で紹介いたします。

#include <VarSpeedServo.h>
#include <LiquidCrystal.h>
VarSpeedServo myservo1;
LiquidCrystal lcd( 12, 11, 5, 4, 3, 2 );
int volumePin = 3;
int switchPin = 9;
int operationswPin = 10;
int totalTime = 20 ;
int firstStep = 2 ; 
int lastStep = 1 ; 
int lowFlame = 180 ;
int midFlame = 90 ;
int highFlame = 0 ;
unsigned long startTime = 0;
unsigned long nowTime = 0;
int m = 0;
int s = 0;
int valAng = 0 ;
int valTemp = 0 ;
int valAngprint = 0 ;
boolean initialSet = true;
boolean highSet = true;

void setup() {
  Serial.begin(9600);
  pinMode(switchPin, INPUT) ;
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("Auto rice cooker");
  lcd.setCursor(0, 1);
  lcd.print("  with arduino  ");
  delay(3000);
  lcd.clear();
  myservo1.attach(7);
}

void loop() {
  Serial.print (nowTime) ;
  Serial.print ("  ") ;
  Serial.print (m) ;
  Serial.print ("  ") ;
  Serial.print (s) ;
  Serial.print ("  ") ;
  Serial.print (valAng) ;
  Serial.print ("  ") ;
  Serial.println (valTemp) ;

  if (digitalRead(switchPin) == HIGH) {
    initialSet = !initialSet ;
    delay(50);
    while (digitalRead(switchPin) == HIGH) {}
  } else {
    if (initialSet == true) {
      myservo1.attach(7);
      startTime = millis();
      if (digitalRead(operationswPin) == HIGH) {
        highSet = !highSet ;
        delay(50);
        while (digitalRead(operationswPin) == HIGH) {}
      } else {
        lcd.setCursor(0, 0);
        lcd.print("Adjust the flame");
        valAng = 180 - analogRead(volumePin) * 3 / 17 ; // 0 to 1024 change into angle
        valAngprint = 180 - valAng;
        if (valAngprint < 10) {
          lcd.setCursor(12, 1);
          lcd.print("   ");
          lcd.setCursor(15, 1);
          lcd.print(valAngprint);
        } else if (valAngprint < 100) {
          lcd.setCursor(12, 1);
          lcd.print("  ");
          lcd.setCursor(14, 1);
          lcd.print(valAngprint);
        } else {
          lcd.setCursor(12, 1);
          lcd.print(" ");
          lcd.setCursor(13, 1);
          lcd.print(valAngprint);
        }
        if (highSet == false) {
          lcd.setCursor(0, 1);
          lcd.print("LOW setting ");
          lowFlame = valAng ;
          valTemp = lowFlame ;
          myservo1.write(valTemp, 30, true);
        } else {
          lcd.setCursor(0, 1);
          lcd.print("High setting");
          highFlame = valAng ;
          valTemp = highFlame ;
          myservo1.write(valTemp, 30, true);
        }
      }
    } else {
      nowTime = totalTime * 60000 + startTime - millis();
      m = nowTime / 60000;
      s = (nowTime % 60000) / 1000;
      if (highSet == false) {
        lowFlame = 180 - analogRead(volumePin) * 3 / 17 ;
      } else {
        highFlame = 180 - analogRead(volumePin) * 3 / 17 ;
      }
      midFlame = (highFlame + lowFlame) / 2 ;
      valAngprint = 180 - valTemp;
      lcd.setCursor(15, 1);
      lcd.print(" ");
      if (valAngprint < 10) {
        lcd.setCursor(12, 1);
        lcd.print("   ");
        lcd.setCursor(15, 1);
        lcd.print(valAngprint);
      } else if (valAngprint < 100) {
        lcd.setCursor(12, 1);
        lcd.print("  ");
        lcd.setCursor(14, 1);
        lcd.print(valAngprint);
      } else {
        lcd.setCursor(12, 1);
        lcd.print(" ");
        lcd.setCursor(13, 1);
        lcd.print(valAngprint);
      }
      if (m < 10) {
        lcd.setCursor(0, 0);
        lcd.print(" ");
        lcd.setCursor(1, 0);
        lcd.print(m);
      } else {
        lcd.setCursor(0, 0);
        lcd.print(m);
      }
      lcd.setCursor(2, 0);
      lcd.print(":");
      if (s < 10) {
        lcd.setCursor(3, 0);
        lcd.print("0");
        lcd.setCursor(4, 0);
        lcd.print(s);
      } else {
        lcd.setCursor(3, 0);
        lcd.print(s);
      }
      if (m > 18) {
        lcd.setCursor(5, 0);
        lcd.print("           ");
      } else  if (m > 16) {
        lcd.setCursor(6, 0);
        lcd.write(">");
      } else  if (m > 14) {
        lcd.setCursor(7, 0);
        lcd.write(">");
      } else  if (m > 12) {
        lcd.setCursor(8, 0);
        lcd.write(">");
      } else  if (m > 10) {
        lcd.setCursor(9, 0);
        lcd.write(">");
      } else  if (m > 8) {
        lcd.setCursor(10, 0);
        lcd.write(">");
      } else  if (m > 6) {
        lcd.setCursor(11, 0);
        lcd.write(">");
      } else  if (m > 4) {
        lcd.setCursor(12, 0);
        lcd.write(">");
      } else  if (m > 2) {
        lcd.setCursor(13, 0);
        lcd.write(">");
      } else  if (m > 0) {
        lcd.setCursor(14, 0);
        lcd.write(">");
      }  else {
        lcd.setCursor(15, 0);
        lcd.write(">");
      }
      if ( nowTime > totalTime * 60000) {
        myservo1.write(180, 30, true);
        myservo1.detach();
        lcd.setCursor(0, 0);
        lcd.print(" Cook finished. ");
        lcd.setCursor(0, 1);
        lcd.print("  Valve closed  ");
        delay(500) ;
      } else if (m >= totalTime - firstStep) {
        if (abs(valTemp - midFlame) < 1) {
          myservo1.stop();
        } else {
          valTemp = midFlame ;
          myservo1.write(valTemp, 30, true);
        }
        lcd.setCursor(0, 1);
        lcd.print("1. Mid level");
      } else if (nowTime >= lastStep * 60000) {
        if (abs(valTemp - lowFlame) < 1) {
          myservo1.stop();
        } else {
          valTemp = lowFlame ;
          myservo1.write(valTemp, 30, true);
        }
        lcd.setCursor(0, 1);
        lcd.print("2. Low level");
      } else if (nowTime > 0) {
        if (abs(valTemp - highFlame) < 1) {
          myservo1.stop();
        } else {
          valTemp = highFlame ;
          myservo1.write(valTemp, 30, true);
        }
        lcd.setCursor(0, 1);
        lcd.print("3. Final HI ");
      } else {
        myservo1.write(180, 30, true);
        myservo1.detach();
        lcd.setCursor(0, 0);
        lcd.print(" Cook finished. ");
        lcd.setCursor(0, 1);
        lcd.print("  Valve closed  ");
        delay(500) ;
      }
    }
  }
}

 

キャンプでシュラスコ!電動ロティサリーをArduinoとステップモーターで自作

ソロキャンプでも、ロティサリーチキンのような回転焼きを電動で楽しみたい!と思い、ステップモーターとArduinoで、小型の電動ロティサリー(回転BBQ器)を自作してみました。

ロティサリーBBQをコンパクトに楽しもう!Arduinoと28BYJ-48で小型の電動回転丸焼き器を自作

ソロキャンプで焼くもの

ソロキャンプでは、チキンの丸焼きは食べきれませんが、手羽やモモ肉など小さめの食材であれば、ロティサリーを使って網焼きとは違った食感が楽しめそうです。

屋外でのBBQでは、網を使ってソーセージを焼くことが多いですが、曲がっているソーセージは焼きムラができてしまい、曲がっていなくても網の上で転がっていってしまうことがあります。

ロティサリーを使えば、ソーセージのような形のものでも、転がる心配をせずに全周を均一に焼くことができそうです。

小型のロティサリーマシンを自作します

ソロキャンプ用の電動ロティサリーで、食べることだけでなく、食材がクルクル回りながら食べごろに焼きあがる過程も、見て楽しみたいと思います。

出力軸を低速回転で動かすことができれば、簡単な工作でつくれそうです。

ソロキャンプスタイルにちょうどいい大きさの電動ロティサリーを自作してみます。

部品の準備

動力部は、低速でも定電圧で動かせるステップモーター28BYJー48を、Arduinoで速度制御して動かします。

f:id:solocamptouring:20181008230435j:plain

回転速度は、可変抵抗(ボリューム)で自在に変えられるようにします。

串を脱着する時には、可変抵抗は操作せずに回転を止めたいので、電源用のスイッチを設置したいところです。

しかし、お気に入りのミサイルスイッチの入手が間に合わなかったので、当分はモバイルバッテリー側のスイッチで代用します。

  • Arduino
  • ステップモーター
  • スイッチ
  • 可変抵抗

駆動力の伝達部は、ユニバーサルジョイント2個で繋いで、BBQ台にのせる串と駆動部の出力軸との軸位置ズレを吸収します。

伝達軸は、ステップモーターの出力軸径と同じ大きさのφ5mmのアルミ棒を使います。

  • φ5mmアルミ棒
  • 同軸ジョイント#1(モーターとアルミ棒)
  • ユニバーサルジョイントx2個
  • 同軸ジョイント#2(アルミ棒と串)

f:id:solocamptouring:20181007064334j:plain

その他、モーター格納用のボックスや、同軸ジョイント#2と串を固定するための、手で締め付けられるネジなども使用します。

機構部を作ります

動力伝達部のジョイント類は既製品を使うので、ほとんどネジの締め付け作業だけで組み上げられます。

軸位置のズレ吸収用ユニバーサルジョイントを2個、動力伝達軸の途中に入れておきます。

f:id:solocamptouring:20181007064435j:plain

串との接続部だけは、様々な串への汎用性を持たせるために穴を拡大したり、手でまわすことができる大きさの締め付け用ボルトを取り付けられるように加工しました。

回路図

Fritzingで回路図をつくりました。

f:id:solocamptouring:20180930164844j:plain

電源はモバイルバッテリーから供給して、スイッチで全供給電源をオンオフするようにします。

Arduinoとモーターは並列に電源供給して、Arduinoに過大な電流が流れないようにしておきます。

回路図左側の可変抵抗で、中央のステップモーターの回転速度を変えられる回路になっています。

回路図の右半分は、完成品で入手できるモータードライバーボードを使いました。

 

Arduinoでステップモーター28BYJー48を回す制御仕様

今回は、可変抵抗でステップモーターのスピードを変える、基本的な手法を使います。

最初に、可変抵抗で調節した電圧を、Arduinoで読み取ります。

Arduinoでは、可変抵抗の電圧を回転速度に変換して、その回転速度に合わせたパルスをステップモータードライバーに出力します。

Arduinoの入出力ポートは5か所しか使わないので、はんだ付け作業も楽ですね! 

f:id:solocamptouring:20181008230805j:plain

28BYJー48とArduinoをボックスに取り付けます

ステップモーターやArduinoを入れるのにちょうどいい大きさの、プロジェクトボックスを入手します。

重量感がある印象にしたいので、アルミのボックスを選択しました。

f:id:solocamptouring:20181014165431j:plain

ステップモーターとスイッチ類を取り付ける穴を開けて、全ての部品を取り付けたら完成です。

ロティサリーチキンの代わりに、ソーセージとたこ焼きで試してみます

ソロキャンプ用のロティサリーなので、焼き鳥サイズの串を回してBBQをします。

ステップモーター28BYJー48は、ロティサリーにちょうどいい回転速度域では、よほど大きく重いものを串に刺さなければ、充分なトルクがあります。

少し軽めのものですが、私のキャンプ飯の定番!、ソーセージで試運転をしてみます。

ソロキャンプでいつも使っている小型ウッドストーブで炭をおこして準備します。

ウッドストーブの外縁2カ所に事務用クリップをつけて、その持ち手部分に串を通して、ソーセージを回します。

ゆっくりと回っているのを見ているだけで、電動ロティサリーを作った満足感で癒されます。

約3分で、表面をほぼ焦がさず、均一に焼き上げることができました。

youtu.be

ソロキャンプでいつも楽しんでいる、冷凍たこ焼きも、ロテイサリーで「表面カリッ、中はトローッ」と焼き上げてみました。

f:id:solocamptouring:20181014165938j:plain

まだトルクに余裕があるので、もっと重いものを焼く時でも使えそうです。

アタッチメントを工夫すれば、手動で回していたバームクーヘンにも応用できそうです。

f:id:solocamptouring:20180926052009j:plain

28BYJー48を実用するスケッチ(プログラム)

最後に、ステップモーター28BYJー48を回すスケッチを紹介いたします。

速度は変えられますが、とにかく回し続けるだけの短いスケッチです。

回転方向は、時計回りと反時計回りの両方に対応できるようにしていますが、実際には時計回りだけで使っています。

int motorPin1 = 4;    // Blue   - 28BYJ48 pin 1

int motorPin2 = 5;    // Pink   - 28BYJ48 pin 2

int motorPin3 = 6;    // Yellow - 28BYJ48 pin 3

int motorPin4 = 7;    // Orange - 28BYJ48 pin 4

// Red    - 28BYJ48 pin 5 (VCC)

int volumePin = 5;

int motorSpeed;

int motorSpeedtimes = 3;

int speedMax = 1500 ;

int waitSpeed = 0 ;

int lookup[8] = {B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001};



void setup() {

  pinMode(motorPin1, OUTPUT);

  pinMode(motorPin2, OUTPUT);

  pinMode(motorPin3, OUTPUT);

  pinMode(motorPin4, OUTPUT);

  Serial.begin(9600);

}

void loop() {

  motorSpeed  = 1024 * (motorSpeedtimes + 1) - (analogRead(volumePin) * motorSpeedtimes) ; //0to1023 change into speed

  clockwise();

}
void anticlockwise() { for (int i = 0; i < 8; i++) { setOutput(i); if ( motorSpeed > speedMax ) { waitSpeed = motorSpeed - speedMax ; delayMicroseconds(speedMax) ; digitalWrite(motorPin1, 0); digitalWrite(motorPin2, 0); digitalWrite(motorPin3, 0); digitalWrite(motorPin4, 0); delayMicroseconds(waitSpeed) ; } else { delayMicroseconds(motorSpeed); } } } void clockwise() { for (int i = 7; i >= 0; i--) { setOutput(i); if ( motorSpeed > speedMax ) { waitSpeed = motorSpeed - speedMax ; delayMicroseconds(speedMax) ; digitalWrite(motorPin1, 0); digitalWrite(motorPin2, 0); digitalWrite(motorPin3, 0); digitalWrite(motorPin4, 0); delayMicroseconds(waitSpeed) ; } else { delayMicroseconds(motorSpeed); } } } void setOutput(int out) { digitalWrite(motorPin1, bitRead(lookup[out], 0)); digitalWrite(motorPin2, bitRead(lookup[out], 1)); digitalWrite(motorPin3, bitRead(lookup[out], 2)); digitalWrite(motorPin4, bitRead(lookup[out], 3)); }

f:id:solocamptouring:20181014164507j:plain

ロティサリーを導入したことで、

  • 焼いて
  • 見て
  • 食べて

の合計3回楽しめるようになり、キャンプの食事時間が、さらに楽しくなりました!

人の動きに同調する1/fゆらぎのつくりかた。USB扇風機を改造してみます(動画あり)

USB扇風機を癒しの1/fゆらぎ風に改造して、パソコン前スペースを快適化

空調の効いた屋内でも、いつも風を感じていたい!ので、パソコンのUSB端子を電源とする小型扇風機を使ってみる事にしました。

せっかくなので、自然風のような1/fゆらぎの風を感じられるように改造してみます。

1/fゆらぎについて

1/fゆらぎとは、振幅が周波数 f に反比例する波の集合波形です。

ゆらぎは平均値からの変動幅なので、周波数が小さい、ゆっくりとした波の成分ほど大きく変動するという意味になります。

人体も1/fゆらぎをしていて、周りの1/fゆらぎ を感じると、共鳴して精神安定などの効果があると考えられています。

USB電源のミニ扇風機

改造ベースとなる扇風機は、しっかり風を感じられて静かなものを選びます。

飛行機でSBも採用例がある二重反転プロペラ(コントラペラ)で、効率よく空気を押し出すものが良いようです。

たまたま、実際に使っている人のパソコン前を、静かなので扇風機に気付かずに通った時に、予想外の風量に驚かされたこともあり、写真の二重反転プロペラを持つ扇風機を選びました。

f:id:solocamptouring:20180719045658j:plain

リズム時計製のコントラペラ扇風機で、他にも「くまのプーさん」や「ミッキー」をデザインモチーフとしたバージョンもあります。

私は、一番安かったオーソドックスな形の薄紫色をチョイスしました。

この扇風機は、風量をハイ/ローの二段階に切り替えられます。

ローはそのまま使えるようにして、ハイに切り替えると1/fゆらぎの風になるように改造します。

Arduino と赤外線センサーで1/fゆらぎをつくる

1/fゆらぎの風は、扇風機と人との距離に応じて変動するようにします。

基本は、距離が

  • 近いと弱風
  • 遠いと強風
  • さらに遠いと停止(OFF)

にします。

赤外線センサーは、正面の平面体に対しては安定して距離を測れますが、服などの凹凸物に対しては少しばらつきがある測定結果になります。

これを逆手にとって、ばらつきが

  • 大きい時は、ゆっくり風量を変える
  • 小さい時は、早く風量を変える

ことで、1/fゆらぎの風をつくります。

これをモーターの回転数を変化させて実現するために、Arduinoを使って赤外線センサーで計測した扇風機前のもの(人)との距離から、モーターの目標スピードと、目標スピードに変わるまでの時間を決めて、PWM制御のパルスで出力します。

PWM(Pulse width modulation、パルス幅変調)は、電圧一定で、周期も一定のパルス波形です。このパルスを電源に使いつつ、ONパルスの幅を変える(=デューティ比を変える)と電圧の平均値が変わるため、結果として電圧を変えた時と同じ効果があります。

Arduino を使う回路

回路図は、Fritzingで作成しました。

f:id:solocamptouring:20180722133259j:plain

赤外線センサーGP2Y0E02Aの接続

シャープの、赤外線式の測距モジュールGP2Y0E02Aには、4本の端子があります。

  1. VDD(3.3V電源)
  2. GND
  3. Vout(出力)
  4. GPIO1

1番はArduinoの3.3Vに、2番はGNDに接続します。

3番のVoutは、電圧で距離情報を返してくるので、Arduinoのアナログピン(回路図ではA3ピン)に接続します。

4番のGPIO1は、電圧がVDD付近であれば計測、GND付近であればスタンバイ(計測しない)モードとなります。Arduinoのデジタルピン(回路図ではD5ピン)からの5V信号を、抵抗3個とダイオードで約3.3Vに減圧して接続します。

赤外線センサーGP2Y0E02Aの接続(USB扇風機の癒し改造)

モーターの接続

PWMで速度を変えるため、Arduinoのデジタルピン7からパルス信号を出力します。

パルス信号はトランジスタのベースに送ってスイッチングして、モーターに電流供給します。

パルスがLOW(0V)の時に、モーターの逆起電力からトランジスタを守るために、ダイオード(D1)を、モーターと並列に使用します。 

トランジスタとベース抵抗値の計算

トランジスタ2SD882を使う回路設計

使用するUSB扇風機は、商品説明によると定格電流210mAです。

トランジスタは、余裕を持って作動させられる2SD882を使います。

  • Ic=210mA
  • hFE=100倍
  • VBE=0.8V

の値を使って、ベースに接続する抵抗値を求めます。

RB=(5-0.8)/(0.21/100)=2000Ω

2000Ωの抵抗RBを使用して、トランジスタのベースとArduinoのデジタルピン(D7)を接続します。 

作動を安定させるため、抵抗RBと平行にコンデンサを、またトランジスタのBE間に抵抗を入れておきます。

扇風機から、そのまま使う電子部品

f:id:solocamptouring:20180719050203j:plain

使用するUSB扇風機の、ハイ/ロー切り替えの元回路は、

  • ハイ側: 電源は、2個の直列配置モーターに直接供給
  • ロー側: 2個の定電流ダイオード(直列)を通して、2個の直列配置モーターに電源供給

となっていました。

ロー側の定電流ダイオードは、電源のプラス側からマイナス側に移設して、そのまま使います(回路図では、2個をまとめてD4と記載)。

スイッチをハイ側に切り替えると、Arduinoの電源が入り、トランジスタを通してPWMでモーターを駆動するように、回路を作ります。

USB扇風機にArduinoを搭載して、癒しの1/fゆらぎ風に改造

1/fゆらぎ扇風機のスケッチ(プログラム)

1/fゆらぎのつくりかた

赤外線測距センサーで計測した距離は、100回測定した平均値を使います。

得られた距離に応じて、

  • 5〜60cm: PWMのデューティ比を50%〜100%に変化
  • それ以外: 停止

として、目標速度と、そこに到達するまでの時間を設定します。

また、目標速度に到達するまでの間も距離を測定して、その時点におけるデューティー比のゆらぎ(計算値と、距離測定値から置き換えた数値の差を1/3したもの)を変化量として瞬時に加えます。

デューティー比のゆらぎが負(マイナス)の場合は、数値に応じて短い間、電源を遮断して即答させます。

距離が60cmより遠い場合は、人(私)が扇風機前から席を外したと判断して停止し、以後1秒毎に距離測定して、人が戻ってきたら再開します。

USB扇風機をArduinoで改造

スケッチの紹介

int trigPin = 5;
int pwmOutput = 7;
int distResult = 0;
long distA = 0 ;
int i = 0 ;
int j = 0 ;
int frequencyWindChange = 0 ;
int speedOutput = 0 ;
int speedCurrent = 50 ;
int speedNowTarget = 50 ;
int speedOldGoal = 50 ;
int speedGoal = 50 ;
int speedAdjust = 0 ;
int speedAdjustParameter = 3 ;

void setup() {
pinMode(trigPin, OUTPUT); 
pinMode(pwmOutput, OUTPUT); 
delay (100);
digitalWrite(trigPin, HIGH);
}

void loop() {
  
     long voltAns ;
     voltAns = IDMread(3)  ;           // アナログピン3番のセンサーの値を読込む
     long distAns1 = map(voltAns,0,1023,0,500) ; // 電圧値に変換する(0から5V)
     long distAns2 = map(distAns1,55,220,50,4) ;  // 電圧値から距離に変換する(0.55から2.2Vを50から4cm)
     int distResult = distAns2; 

  if(distResult > 60){
  analogWrite(pwmOutput, 0) ;
  speedOldGoal = 50 ;  
  delay(1000) ;
  }else{
  speedGoal = map(distResult,0,65,50,100) ;
  frequencyWindChange = abs(speedGoal - speedOldGoal) ;

    for (j=0 ; j < frequencyWindChange; j++) {
   speedNowTarget = speedOldGoal + (speedGoal - speedOldGoal)/frequencyWindChange*j ;

      
    long voltAns ;
     voltAns = IDMread(3)  ; 
     long distAns1 = map(voltAns,0,1023,0,500) ; 
     long distAns2 = map(distAns1,55,220,50,4) ; 
     int distResult = distAns2; 
     
     speedAdjust = (map(distResult,0,65,50,70) -  speedNowTarget)/speedAdjustParameter ;
     speedOutput = 255*(speedNowTarget + speedAdjust)/100 ;
     if(speedAdjust < 0){
      analogWrite(pwmOutput,0);
      delay(70*abs(speedAdjust));
     }
     analogWrite(pwmOutput,speedOutput);
     delay(200);
     } 
     
   speedOldGoal = speedGoal ;  
  }
}

// 赤外線測距モジュールから読み込む処理
int IDMread(int PinNo) {
     distA = 0 ;
     for (i=0 ; i < 100; i++) {
          distA  = distA + analogRead(PinNo) ;   // アナログピンから読取る
     } 
     return distA/100 ;                        // 100回の平均値を返す
}

癒しの風を感じてみます

扇風機のプロペラ回転数は細かく変動しています。時々、自然に風のような、ゆっくりとした大きな変動もあり、1/fゆらぎらしくなっています。

回転数に応じてプロペラの残像が刻々と変化するので、ぼ~っと見ていても飽きません。

また、席をはずして戻ってきた時には、停止状態から自動で動き始めるので、「おかえり!」と言われたようで、少し幸せな気持ちになります。

youtu.be

ちょっとした改造ですが、Arduinoと測距センサーを使うことで、オフィスなどの密閉された屋内空間でも、自然のような風のそよぎを感じる事ができて、眠気を誘うほど快適になります。

電動でスライド格納するテーブルの自作。ジムニーの助手席をステッピングモーターで豪華にします

電動スライド開閉式テーブルの自作。JB23ジムニーの助手席を快適で豪華に

JB23ジムニーの助手席への固定式テーブル設置に失敗して、しばらく時間が経ってしまいました。

助手席の人から「カップホルダーがない!」との悲痛な声が多くなってきたので、そろそろカップホルダー付きのテーブルを、初代よりも進化させて自作します。

初代の固定式テーブル

助手席からの評判も良かったのですが、短期間しか使わなかったテーブルです。

f:id:solocamptouring:20180616192634j:plain

初代のコンセプトは、

  • ペットボトルホルダー付き
  • 助手席の乗降の邪魔にならない

の2点でした。

固定位置を上下方向で2カ所設定して剛性を出そうとしたので、勢いで2階建てになっています。そのために上側のテーブル面が、エアバックの展開に影響しそうな位置になってしまい、すぐに外しました。

その後は、良いアイデアが思いつくまでペットボトルホルダーはあきらめて、ソーラーパワー腕時計の充電スタンドをつけた板を、テーブルの代わりに置いていました。

f:id:solocamptouring:20180616193309j:plain

今回の自作テーブルのコンセプト

今回は、先回の時に欠点となっていたエアバックへの影響は無くします。

また、蓋で密閉できるペットボトルは対象とせず、コンビニで手軽に買えるカップコーヒーが置ける事を目指します。

  • カップホルダー付き
  • 助手席の乗降の邪魔にならない
  • エアバックの展開を阻害しない

この3点をコンセプトとして、今回のテーブルを自作します。

テーブルの構造を決める

まず、カップホルダーはテーブル面上に市販のものを、最後に固定することにします。後から取り替えもできるので、テーブル自体が完成するまでは、あまりこだわらないことにします。

テーブル面は格納式にして、乗降性とエアバック展開の条件をクリアします。

さらに、高級感をだすために、電動スライド開閉式に挑戦します。

スライドテーブルの奥行き

JB23ジムニーのグローブボックス上にある凹形状の奥行きは、約10cmです。

グローブボックスの扉面から少し出ていても乗降性に影響しないので、テーブルの台座となる板の奥行きは15cmとします。

台座となる奥行き15cmの板を、MDFから切り出しました。

f:id:solocamptouring:20180616193357j:plain

テーブル面はアクリル板にして、その下に配置する機構部やArduinoなどのLED光が見えるようにします。家で何かないか探したところ、もう使わなくなっていたサンバイザーがでてきました。

f:id:solocamptouring:20180616193421j:plain

このサンバイザー、横幅もちょうどいいので、外形は加工せずにそのまま使います。

テーブルのスライドレール

テーブルを滑らかに動かすためのレールを、安価な引き出し用のスライドレールから探してみましたが、汎用品は20cm以上のものばかりでした。

スライド長さ10cm以下では、アルミの押し出し材を使ったものを唯一みつけたので、早速2個注文しました。

小さなスライド構造DIYに最適な約8cm長のスライドレール
取付用の小さなネジを別に入手しないといけませんが、レール自体は滑らかに動き、強度も充分にありそうです。

電動格納部の自作

ステップモーター28BYJ-48を使う

今回の作品のキモとなる電動開閉機構は、ステップモーター(ステッピングモーター)をArduino で制御するものを自作します。

ステップモーター28BYJ-48は、5Vと12V駆動のものがあります。今回は重力の影響を受けない水平方向の動きで、回転速度も遅くていいので、5V駆動のものを使ってみます。

ステップモーター28BYJ-48をArduino で制御して、電動開閉テーブルを自作

スライド機構

台座の両側に、電動開閉機構部と当たらない高さまで壁をつけて、スライドレールを取り付けます。

テーブルを取り付ける側は、L字断面のプラスチック押し出し材をつけておきます。

f:id:solocamptouring:20180624060332j:plain

台座となる板は、モーターとギヤ用のかさ上げ板を追加して、黒く塗装しておきました。

電動開閉機構

まずは、ステップモーターの出力をギヤで減速します。ギヤは、壊れたプリンターから外したものを、加工して使います

ステップモーター28BYJ-48に合うギヤを、廃品プリンターからリサイクル

不必要に長い軸や邪魔な出っ張りの部分を切り落として、ヤスリで表面を整えました。

減速したドリブン側のギヤにアームを付けて、開閉方向の動きに変えます。

ステップモーターで開閉する機構。電動スライド開閉式テーブルの自作

 

ArduinoとステップモータードライバULN2003を使う回路

Fritzingで回路図を作りました。

Arduinoと28BYJ-48を使う回路図。車用 電動スライド開閉式テーブルの自作。

回路図の右半分は、ステップモーター28BYJ-48と、そのULN2003ドライバーモジュールなので、安く購入できる完成品を使っています。

テーブルを電動開閉するアームの位置情報を得るために、全開と全閉時に押される位置にポジションスイッチ(リミットスイッチ)を使います。それぞれスイッチがオンの時はHIGH(5V)、オフの時はプルダウン抵抗でLOW(約0V)の信号をArduinoに送ります。

操作用のモメンタリースイッチも、別に1個使います。

LEDは、モーターを動かす間だけ光るようにスケッチ(プログラム)を書きます。

ブレッドボード上で動作確認してから、汎用基板で回路を作り台座に搭載しました。

Arduinoと28BYJ-48を使う。電動スライド開閉式テーブルの自作


ポジションスイッチ動画

制御方法

Arduinoで、テーブルの開閉をコントロールします。

開閉の状態切り替え命令は、モメンタリースイッチ使って、順番に

  1. 全閉。
  2. 開く方向に動かす。途中でスイッチが押されることなく全開したらに移行。
  3. (開作動中にスイッチが押されたら)止める。
  4. 閉じる方向に動かす。全閉したらに移行。
  5. 全開。
  6. 閉じる方向に動かす。途中でスイッチが押されることなく全閉したらに移行。
  7. (閉作動中にスイッチが押されたら)止める。
  8. 開く方向に動かす。全開したらに移行。

の状態に切り替えます。途中状態が多くなっていますが、 何か挟まった時等の非常時に、ボタンを押せば止められて、さらにボタンを押せば反転して動くようにするために、8パターンの状態を推移するようにしました。

電源投入時は、全閉/全開検出スイッチの状態によって、

  • 全閉状態なら
  • 全開状態なら
  • 途中状態なら 

から始めます。途中で止まっている状態で電源を入れた場合は、テーブル上に飲み物等があるかもしれないので、次に操作スイッチを押したら開く側の動きにします。

スケッチ(プログラム)は最後に紹介します。

完成!(動画あり)

スケッチをArduinoに書き込んだら、車に取り付けて動かしてみます。

定電圧で低回転速度をつくれるステップモーターだからできる、静かで力強い動きです。


Arduino project #4 「車の助手席用の電動格納テーブルをつくる」Making auto-open/close table for passenger seat of car

出力軸の形状が特殊なのでギヤの入手が難しいですが、それをクリアできれば28BYJ-48は、静かにゆっくり動かしたい物を自作する時に重宝するモーターです。

スケッチ(プログラム)の紹介

int operationledPin = 3;    //  operation indicator led output
int motorclosePin = 8;    // full close position sw input
int motoropenPin = 9;     // full open position sw input
int operationswPin = 10;    // operation sw input
int powerOnled = 12;    // power on LED

int motorPin1 = 4;    // Blue   - 28BYJ48 pin 1
int motorPin2 = 5;    // Pink   - 28BYJ48 pin 2
int motorPin3 = 6;    // Yellow - 28BYJ48 pin 3
int motorPin4 = 7;    // Orange - 28BYJ48 pin 4
                      // Red    - 28BYJ48 pin 5 (VCC)

int motorSpeed;  
int count = 0;          // count of steps made
int speedcontr = 0;
int countstop = 320; // counts to stop to avoid heat
int conditionNumber = 0; // 0:close  1:move to open  2:stop to close  3:move to close  4:open  5:move to close  6:stop to open  7:move to open
int lookup[8] = {B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001};

void setup() { 
  pinMode(operationledPin,OUTPUT) ; 
  pinMode(motorclosePin,INPUT) ; 
  pinMode(motoropenPin,INPUT) ; 
  pinMode(operationswPin,INPUT) ;
  //declare the motor pins as outputs
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorPin3, OUTPUT);
  pinMode(motorPin4, OUTPUT);
  Serial.begin(9600);

  if (digitalRead(motoropenPin) == HIGH){
  conditionNumber = 4; // table is open
  } else if (digitalRead(motorclosePin) == HIGH){
  conditionNumber = 0; // table is close
  } else {
  conditionNumber = 6; // table is middle next open    
  }
 digitalWrite(powerOnled , HIGH); 
}

void loop() {

if (digitalRead(operationswPin) == HIGH) { 
    if(conditionNumber<7){   
    conditionNumber++;
    }else {
    conditionNumber = 0;   
    }
    delay(50);
    while(digitalRead(operationswPin)==HIGH){}
}

if(conditionNumber == 0){
  if (digitalRead(motorclosePin) == HIGH){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;  
  } else {
  digitalWrite(operationledPin, HIGH);    
  speedcontr = (3*count);
  motorSpeed = (2000+speedcontr);
  anticlockwise();
  count++;  
  }

}else if(conditionNumber == 1){
  if (digitalRead(motoropenPin) == HIGH){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;
  conditionNumber = 4;
  } else {
  digitalWrite(operationledPin, HIGH);    
  speedcontr = (3*count);
  motorSpeed = (2000+speedcontr);
  clockwise();
  count++;  
  }

}else if(conditionNumber == 2){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;

}else if(conditionNumber == 3){
  if (digitalRead(motorclosePin) == HIGH){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;
  conditionNumber = 0;
  } else {
  digitalWrite(operationledPin, HIGH);    
  speedcontr = (3*count);
  motorSpeed = (2000+speedcontr);
  anticlockwise();
  count++;  
  }

} else if(conditionNumber == 4){
  if (digitalRead(motoropenPin) == HIGH){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;  
  } else {
  digitalWrite(operationledPin, HIGH);    
  speedcontr = (3*count);
  motorSpeed = (2000+speedcontr);
  clockwise();
  count++;  
  }

}else if(conditionNumber == 5){
  if (digitalRead(motorclosePin) == HIGH){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;
  conditionNumber = 0;
  } else {
  digitalWrite(operationledPin, HIGH);    
  speedcontr = (3*count);
  motorSpeed = (2000+speedcontr);
  anticlockwise() ;
  count++;  
  }

}else if(conditionNumber == 6){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;

}else if(conditionNumber == 7){
  if (digitalRead(motoropenPin) == HIGH){
  digitalWrite(operationledPin, LOW);    
  digitalWrite(motorPin1,LOW);
  digitalWrite(motorPin2, LOW);
  digitalWrite(motorPin3, LOW);
  digitalWrite(motorPin4, LOW);
  count = 0;
  conditionNumber = 4;
  } else {
  digitalWrite(operationledPin, HIGH);    
  speedcontr = (3*count);
  motorSpeed = (2000+speedcontr);
  clockwise();
  count++;  
  }
}
}

void anticlockwise() 
{
  for(int i = 0; i < 8; i++)
  {
    setOutput(i);
    delayMicroseconds(motorSpeed);
  }
}

void clockwise()  
{
  for(int i = 7; i >= 0; i--)
  {
    setOutput(i);
    delayMicroseconds(motorSpeed);
  }
}

void setOutput(int out)
{
  digitalWrite(motorPin1, bitRead(lookup[out], 0));
  digitalWrite(motorPin2, bitRead(lookup[out], 1));
  digitalWrite(motorPin3, bitRead(lookup[out], 2));
  digitalWrite(motorPin4, bitRead(lookup[out], 3));
}

トイラジコンを自動走行ロボットにする。超音波センサー5個とArduinoで工作

自動運転の車が実際に走る社会が近づいてきました。5千円ほどの材料を使っての簡単な工作レベルで、どこまで自動走行ができるのかを試してみます。

トイラジコンを自動走行ロボットにする。超音波センサー5個とArduinoで工作

自動運転(走行)で楽しくなる

自動運転と聞くと、スタート地点からゴール地点まで無人で走る車を思い浮かべます。そんな社会になったら移動時間の使い方の幅が多いに広がります。

もっと細かい分野では、自動ブレーキ、自動速度(全車との間隔)制御、自動駐車など、既に実用化されているものがあります。

さらに屋内では、お掃除ロボットが走りまわっている家も多くなっています。

楽になることはいいのですが、趣味の世界では楽しくないといけない!と思うので、今回自作する自動走行ロボットは、少しコミカルな動きを取り入れて、見ていて面白いものを目指します。

自動走行の動作

どのような動かし方をするかを決めます。

今回使うセンサーや認識ツールは、画像処理のような高価な技術は使わずに、一個あたり数百円で入手できる超音波センサーのみとします。

そのため、動作もシンプルで破綻(動けなくなる)しにくいものにします。

  • 障害物との距離が遠い方に操舵する
  • 前方障害物との距離に応じてスピードを変える
  • 行き詰まったら、少し後退して回転する

自動走行ロボットは、市販のトイラジコンを改造して作りますが、元のコントローラも有効利用します。

  • 2個のシーソースイッチを全後進の右旋回と左旋回ボタンとして、割り込み操作可能にする

ベースマシンの選定

ニッコー マックストラックス NIKKO MAXX TRAXX

改造ベースとするトイラジコンを選びます。

屋外で自動走行させる予定ですが、障害物の乗り越え性能はそこそこで良しとします。

少ないセンサーで、障害物の間に挟まったりして破綻しない為には、超信地旋回が有利です。そのため、タイヤで駆動し操舵するものではなく、モーターが2個になってしまいますが無限軌道タイプにします。

早速調べてみたところ数千円で入手できる無限軌道のトイラジコンをみつけました。

速度は可変せず、左右それぞれのモーターに対して前進-停止-後進の3パターンしかありません。

f:id:solocamptouring:20180602092718j:plain

常にフルパワーでは制御しにくいので、自動走行時は障害物との距離に応じて、Arduinoを使ってPWMで出力(速度)可変できるようにする計画です。

(補足)PWMについての簡単な説明

DCモーターの出力(回転数)は、モーター端子間の電圧を変えることでコントロールできます。

PWM(Pulse width modulation、パルス幅変調)は、電圧一定で、周期も一定のパルス波形にしたものです。これにより、電源電圧は一定でも、パルスの幅を変えれば電圧の平均値が変わるため、結果として電圧を変えた場合と同じ効果があります。

トイラジコンの分解調査

手元に届いて簡単に作動確認後、すぐに分解します。

電子回路基板

ニッコー マックストラックス(NIKKO MAXX TRAXX)の電子回路

トイラジコンに搭載されている基板についてネットで調べてみると、回路図が見つかりました。基板をラジコン用汎用モジュールとして取り扱っているようです。

写真中央のチップが、左のアンテナ(短いハーネス)から受け取った信号を処理する頭脳部です。

写真下の方にある、モーターからの配線が繋がっている2個のチップが、Hブリッジ回路が内蔵されたモータードライバICのようです。

この2個のチップにArduinoからパルスを入力すれば、PWM制御で速度コントロールができると思われます。

モーター

ニッコー マックストラックス(NIKKO MAXX TRAXX)の中身

直径約25mmほどのモーターが2個使われています。

左右駆動モジュール化されており、モーター単体レベルまで分解してはいないので、メーカーや型式は不明です。

制御しやすいステッピングモーターで同じサイズのものがあれば載せ替えたかったのですが、簡単に載せ替えられそうなものは無く、またギヤボックスへの取り付けやギヤの入手に苦労しそうな為、このままDCモーターを使ってPWMで速度制御してみます。

Arduino と超音波センサー5個の配線

超音波センサーモジュールについては、別の記事でスケッチ(プログラム)の使い方や得られる距離データを確認しました。

自動走行ロボットには、前向きに3個と後ろ向きに2個の、合計5個のセンサーを配置して、それぞれの正面にある障害物との距離を測ります。

複数の超音波センサーで距離を測る場合、通常は音の干渉による誤測定を防止するために1個づつ順番に音を出して測定していきます。

今回は、5個のセンサーは全て違う方向を向いているので、5個同時に音を出してみます。

Arduino を使う回路図

トイラジコンを自動走行ロボットにするArduinoスケッチ

回路図はFritzingで作成しています。

回路図右下のICが、トイラジコンのモータードライバー2個をまとめて現したものです。

このICに、 Arduino からパルスを入力する事で、モーターの出力を可変制御します。

  • D5 : 右側モーター後進用PWMパルス出力
  • D6 : 右側モーター直進用PWMパルス出力
  • D9 : 左側モーター直進用PWMパルス出力
  • D11: 左側モーター後進用PWMパルス出力

超音波センサーは、上の3個でロボットの前側、左の2個で後ろ側の距離を測定します。

  • D2 : 超音波センサー5個共通のトリガー出力
  • D7 : 前右側の超音波センサーのエコー入力
  • D8 : 前中央の超音波センサーのエコー入力
  • D10: 前左側の超音波センサーのエコー入力
  • D3 : 後ろ右側の超音波センサーのエコー入力
  • D4 : 後ろ左側の超音波センサーのエコー入力

トイラジコンに付属のリモコンからの全後進x2チャンネルの信号を、ラジコン頭脳チップから入力します。

  • A0(D14): リモコンの右側後進命令信号の入力
  • A1(D15): リモコンの右側前進命令信号の入力
  • A2(D16): リモコンの左側前進命令信号の入力
  • A3(D17): リモコンの左側後進命令信号の入力

自動走行ロボットの雰囲気を出すために、LEDを光らせます。

1個は超音波測定時に点灯して、状態をモニターできるようにします。

さらに1個を、前方の近距離に障害物があることを検知している場合に点灯します

  • A4(D18): 超音波測定中を表示するLEDへの出力
  • A5(D19): 障害物検知を表示するLEDへの出力

(補足)デジタルピンが足りない場合にアナログピンを使う方法

上の回路図説明で、リモコン操作による前後進のデジタル信号をアナログピンに入力しています。またLEDを光らせるデジタル信号のHIGH(5V)をアナログピンから出力しています。

私がよく使うArduino unoやnanoは入出力ピンの数が限られています。そこで、アナログピンをデジタルピンとして扱えるという便利な機能を使います。

アナログピン0~5には、14から19までのデジタルピン番号がついているので、たとえばアナログピン0をデジタルピン14の入力に使うことを宣言して、その状態(HighかLow)を読みます。

   pinMode(14, INPUT);
   digitalRead(14);

INPUTをOUTPUTに変えて、デジタル出力として使うこともできます。

   pinMode(18, OUTPUT);
   digitalWright(18, HIGH);

スケッチ(プログラム)全文は、最後に紹介いたします。

ロボット作りの作業開始

センサー部をつくる

f:id:solocamptouring:20180602172552j:plain

今回の作品はArduinoのピンをほぼ使い切っているだけあって、はんだ付け作業がかなり多いです。

最初に、合計5個のセンサーを、前向き用3個、後ろ向き用2個に分けて、それぞれ汎用基板にはんだ付けして配線します。

できたものがこちらです。

トイラジコンを自動走行ロボットにするための超音波センサー5個

それぞれの角度に固定されると、一段とセンサーらしさが増した気がします。

トイラジコンには、樹脂のホルダーやステーを使って装着します。前側は、センサー5個にオマケでついてきたホルダーを使いました。

トイラジコンの前側に超音波センサー3個を装着。自動走行ロボット化計画

後ろ側のセンサーは、余っていたL字押し出し材を使って、トイラジコンに固定します。使いまわしているので穴だらけですが、性能には影響しないので、このまま使います。

トイラジコンの後ろ側に超音波センサー2個を装着。自動走行ロボット化計画

トイラジコンの基盤に割り込み

ArduinoからのPWM用パルスの入力と、リモコン操作信号をArduinoに送る出力のために、トイラジコンの基盤回路に割り込みます。

まずは、基盤のモーター制御用の信号線を削って切断します。

f:id:solocamptouring:20180603190820j:plain

写真の右側が信号を出すIC、左側がモータードライバーです。

IC側は、Arduinoに送る信号線をはんだ付けできるように、少しプリント導線を露出させておきます。

モータードライバー側は、丸く導線露出した部分がはんだ付けにちょうどいいので、使わせていただきす。

f:id:solocamptouring:20180603191412j:plain

写真下部の工場出荷時のはんだ付けが少し雑ですが、上部の自分で作業した部分も同レベルなので、そのままにしました。

追加したはんだ付け部は、あとでテープを使い絶縁保護しました。

Arduinoを搭載

配線本数が多いので、とにかく黙々とはんだ付けを続けます。

完成したらArduinoを搭載した基板はボックスに入れたいので、とりあえずトイラジコンにプロジェクトボックスの蓋を貼り付けて、さらにその上にArduinoを両面テープで貼り付けました。

Arduinoをトイラジコンに装着して自動走行ロボット完成

むき出し感がありますが、完成です!

自動走行 By アルドゥイーノ

早速、動かしてみます。家の中だとモーター音が響いて家族に驚かれるので、外で試験走行しました。

PWMでスピードを落としていますが、それでも速すぎて距離検知が遅れぎみです。

検知が追い付いつかないので暴れん坊のような動きになってしまい、自動走行というよりもランダム走行に見えます。

youtu.be

後退時に後ろの障害物に当たりにいってしまうのも、改善点です。

初回のスケッチ(プログラム)にしては動いている方だと前向きにとらえて、今後も自動走行に見えるレベルまで、プログラムや定数を改善していきます。

低速モーター化(後日談)

その後、少し間が開いてしまいましたが、低速走行を可能にするために、ステップモーターに組み替えました!

いい感じの速度とトルクになっています。

 

www.solocamptouring.com

 

トイラジコンを超音波センサー5個とArduinoで工作自動走行ロボットにしてみます

スケッチ(プログラム)の紹介

現時点での暴れん坊バージョンのスケッチを紹介いたします。

// 全てのセンサーを1発のトリガーで動かす
int trigPin = 2;
// 前3個のセンサーのエコー
int echoFr = 7; // front right
int echoFc = 8; // front center
int echoFl = 10; // front left
// 後ろ2個のセンサーのエコー
int echoRr = 3; // rear right
int echoRl = 4; // rear left

// PWMパルス出力
int pwmRf = 6; // right 前進
int pwmRr = 5; // right 後進
int pwmLf = 9; // left 前進
int pwmLr = 11; // left 後進

// リモコンの割り込み入力
int remoteRf = 15; // right 前進
int remoteRr = 14; // right 後進
int remoteLf = 16; // left 前進
int remoteLr = 17; // left 後進

// LED出力
int measurLed = 18; // 測定中に光るLED
int detectLed = 19; // 障害物検知中に光るLED

// モーター出力を定義
int maxSpeed = 100; // 最大出力を255以下で定義
int midSpeed = 90; // 中間出力を255以下で定義
int lowSpeed = 80; // 最小」出力を255以下で定義

// その他
boolean inReverse =false; // 後退中は真
int distSteer = 50; // 方向を変える横方向距離
int distStop = 50; // 危険と判断して止める距離
int distClear = 60; // 危険が無くなったと判断する距離
int maxStraight = 300; // 全速を許可する時の前方安全範囲
int nMeasure = 1; // 平均値をとるための計測回数

void setup() {

pinMode(trigPin, OUTPUT); 
pinMode(pwmRf, OUTPUT); 
pinMode(pwmRr, OUTPUT); 
pinMode(pwmLf, OUTPUT); 
pinMode(pwmLr, OUTPUT); 

pinMode(echoFr, INPUT);
pinMode(echoFc, INPUT);
pinMode(echoFl, INPUT);
pinMode(echoRr, INPUT);
pinMode(echoRl, INPUT);
pinMode(remoteRf, INPUT);
pinMode(remoteRr, INPUT);
pinMode(remoteLf, INPUT);
pinMode(remoteLr, INPUT);

Serial.begin(9600);

digitalWrite(measurLed, LOW); 
digitalWrite(detectLed, LOW); 
delay(2000);
}

void loop() {

if(digitalRead(remoteRf) == digitalRead(remoteRr) && digitalRead(remoteLf) == digitalRead(remoteLr)) 
{
  //リモコンの割り込み操作がなければ距離測定を開始

digitalWrite(measurLed, LOW); 

 if(inReverse == false){
 //前進
 int distanceFr;
 int distanceFc;
 int distanceFl;
 
 long durationFr;
 long durationFc;
 long durationFl;
 
 int distAFr = 0;
 int distAFc = 0;
 int distAFl = 0;

 int distFr ;
 int distFc ;
 int distFl ;

 int i ;
    for (i=0 ; i < nMeasure ; i++) {
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFr = pulseIn(echoFr, HIGH);// echoピンから帰ってくる数値mSを読む
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFc = pulseIn(echoFc, HIGH);
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFl = pulseIn(echoFl, HIGH);
    
    digitalWrite(measurLed, HIGH);
         
// 距離計算
    if(durationFr == 0){
    distanceFr = 250;
    } else {
    distanceFr= durationFr*0.034/2;
    }
    if(durationFc == 0){
    distanceFc = 250;
    } else {  
    distanceFc= durationFc*0.034/2;
    }
    if(durationFl == 0){
    distanceFl = 250;
    } else {    
    distanceFl= durationFl*0.034/2;
    }
 
    distAFr  = distAFr + distanceFr ;
    distAFc  = distAFc + distanceFc ;
    distAFl  = distAFl + distanceFl ;
    } 
    
  distFr = distAFr/nMeasure  ;   // 平均値をアウトプット
  distFc = distAFc/nMeasure  ; 
  distFl = distAFl/nMeasure  ;

 Serial.print(distFr);
 Serial.print("   ");
 Serial.print(distFc);
 Serial.print("   ");
 Serial.print(distFl);
 Serial.println("DRIVE");
 
digitalWrite(measurLed, LOW); 

  if( distFc < distStop){
  inReverse = true ; //前方に進めなければ後進
  } else {
     digitalWrite(detectLed, LOW); 
    if(distFr > distSteer && distFl > distSteer){ //左右が開けていて
      if(distFc > maxStraight){ //前方も開けていたら全開
        analogWrite(pwmRf,maxSpeed);
        analogWrite(pwmRr,0); 
        analogWrite(pwmLf,maxSpeed);
        analogWrite(pwmLr,0); 
      } else { //前方は開けていなければ中速
        analogWrite(pwmRf,midSpeed);
        analogWrite(pwmRr,0); 
        analogWrite(pwmLf,midSpeed);
        analogWrite(pwmLr,0); 
      }
    }else if(distFc > distClear){   //左右が開けていないが、前に充分スペース有
      if(distFr > distFl){
      analogWrite(pwmRf,lowSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,midSpeed);
      analogWrite(pwmLr,0); 
      }else if(distFr < distFl){
      analogWrite(pwmRf,midSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,lowSpeed);
      analogWrite(pwmLr,0); 
      }else{
      analogWrite(pwmRf,midSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,midSpeed);
      analogWrite(pwmLr,0);
      }
   }else{    //左右が開けておらず、前に充分なスペース無し 
      digitalWrite(detectLed, HIGH); 
      if(distFr > distFl){
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,lowSpeed);
      analogWrite(pwmLr,0); 
      }else if(distFr < distFl){
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,lowSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,0); 
      }else{
      analogWrite(pwmRf,lowSpeed);
      analogWrite(pwmRr,0); 
      analogWrite(pwmLf,lowSpeed);
      analogWrite(pwmLr,0);
      }
   }
  }
 } else {
 //後退

    digitalWrite(measurLed, HIGH);   
 int distanceRr;
 int distanceFc;
 int distanceRl;
 
 long durationRr;
 long durationFc;
 long durationRl;
 
 int distARr = 0;
 int distAFc = 0;
 int distARl = 0;

 int distRr ;
 int distFc ;
 int distRl ; 

 int i ;

    for (i=0 ; i < nMeasure ; i++) {
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationRr = pulseIn(echoRr, HIGH);// echoピンから帰ってくる数値mSを読む
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationFc = pulseIn(echoFc, HIGH);
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    durationRl = pulseIn(echoRl, HIGH);

// 距離計算

    if(durationFc == 0){
    distanceFc = 250;
    } else {  
    distanceFc= durationFc*0.034/2;
    }
    if(durationRr == 0){
    distanceRr = 250;
    } else {   
    distanceRr= durationRr*0.034/2;
    }
    if(durationRl == 0){
    distanceRl = 250;
    } else {      
    distanceRl= durationRl*0.034/2;
    }
    
    distAFc  = distAFc + distanceFc ;
    distARr  = distARr + distanceRr ; 
    distARl  = distARl + distanceRl ;
    } 
    
  distFc = distAFc/nMeasure  ;  
  distRr = distARr/nMeasure  ; 
  distRl = distARl/nMeasure  ;

 Serial.print(distFc);
 Serial.print("   ");
 Serial.print(distRr);
 Serial.print("   ");
 Serial.println(distRl);

 Serial.println("REVERSE");

digitalWrite(measurLed, LOW); 
   if(distFc < distClear){
    if(distRr > distSteer && distRl > distSteer){
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,midSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,midSpeed); 
    }else if(distRr > distRl){
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,lowSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,midSpeed); 
    }else if(distRr < distRl){
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,midSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,lowSpeed); 
    }else{
      digitalWrite(detectLed, HIGH); 
      analogWrite(pwmRf,0);
      analogWrite(pwmRr,midSpeed); 
      analogWrite(pwmLf,0);
      analogWrite(pwmLr,midSpeed);
    }
  } else {
  digitalWrite(detectLed, LOW); 
  if(distRr > distRl){
     analogWrite(pwmRf,midSpeed);
     analogWrite(pwmRr,0); 
     analogWrite(pwmLf,0);
     analogWrite(pwmLr,midSpeed);
     delay(100);
       inReverse = false;
  }else{
     analogWrite(pwmRf,0);
     analogWrite(pwmRr,midSpeed); 
     analogWrite(pwmLf,midSpeed);
     analogWrite(pwmLr,0); 
     delay(100);
       inReverse = false;
   }
  }
 }
} else {
//リモコン操作の割り込みがある場合
  if(digitalRead(remoteRf) == HIGH){
    analogWrite(pwmRf,lowSpeed);
    analogWrite(pwmRr,0);
  } else if(digitalRead(remoteRr) == HIGH){
    analogWrite(pwmRf,0);
    analogWrite(pwmRr,lowSpeed);
  } else {
    analogWrite(pwmRf,0);
    analogWrite(pwmRr,0);
  }
  if(digitalRead(remoteLf) == HIGH){
    analogWrite(pwmLf,lowSpeed);
    analogWrite(pwmLr,0);
  } else if(digitalRead(remoteLr) == HIGH){
    analogWrite(pwmLf,0);
    analogWrite(pwmLr,lowSpeed);
  } else {
    analogWrite(pwmLf,0);
    analogWrite(pwmLr,0);
  }
 }
}

赤外線センサーと超音波センサ-で距離をはかってみた。Arduinoで性能比較

arduinoで距離をはかる。赤外線モジュールGP2Y0E02Aと超音波モジュールHC-SR04

初心者でも簡単に電子工作が楽しめるarduino(アルドゥイーノ)を使って実用的な作品作りをしています。

たまには遊び心のある作品もいいじゃない!と思い、近いうちに簡単な自動走行ロボットを作ってみたいと思います。

自動走行を実現するためには、距離センサーが必要です。

まずは、安価で一般的な赤外線モジュールと超音波モジュールを、arduinoを使って実際に距離測定しながら比較して、ロボットへの搭載位置や個数を決める時に活用します。

Arduinoで距離をはかる。赤外線モジュールGP2Y0E02Aと超音波モジュールHC-SR04

赤外線モジュールGP2Y0E02A(左)と超音波モジュールHC-SR04(右)

赤外線センサー GP2Y0E02A

シャープの赤外線式の測距モジュールとしては、GP2Y0A21YKがArduino作品でよく使われます。

今回はもっと小型のものを試してみたいと思い、3.3V電源で使用する測距モジュールモジュールGP2Y0E02Aを選択しました。

18.9 × 8.0 × 5.2mmとたいへんコンパクトなモジュールですが、赤外線LEDとCMOSイメージセンサーを備えています。

4~50cmの距離を高精度に素早く測定できますが、赤外線がしっかり反射する材質と面積と位置(角度)の相手物でないと測定できません。つまり、

  • 測定が早くて正確
  • 測定する相手物は、真正面
  • 相手物にはセンサーと平行の赤外線反射面が必要
  • 反射面の面積がある程度必要

なので、例えば決められたルートや迷路を自動走行するロボットなどには使えそうです。

ArduinoとGP2Y0E02Aの配線図

Arduinoと赤外線モジュールGP2Y0E02Aの配線図
Arduinoと赤外線モジュールGP2Y0E02Aの配線

回路図はFritzingで作成しています。

赤外線式距離モジュールGP2Y0E02Aには、4本の端子があります。

  1. VDD(3.3V電源)
  2. GND
  3. Vout(出力)
  4. GPIO1

1番と2番はArduinoの3.3VとGNDに接続します。

3番のVoutは、電圧で距離情報を返してくるので、Arduinoのアナログピン(回路図ではA3ピン)に接続します。

4番のGPIO1は、電圧がVDD付近であれば計測、GND付近であればスタンバイ(計測しない)モードとなります。今回はお試しなので簡易的に1番のVDDに直接つなぎます。

GP2Y0E02Aを使うスケッチ(プログラム)

void setup() {
 Serial.begin(9600);
}

void loop() {
delay (100);
     long distAns ;
     distAns = IDMread(3)  ;           // アナログピン3番のセンサーの値を読込む
     long distAns1 = map(distAns,0,1023,0,500) ; // 電圧値に変換する(0から5V)
     long distAns2 = map(distAns1,55,220,50,4) ;  // 電圧値から距離に変換する(0.55から2.2Vを50から4cmに)
     int distResult = distAns2; 
     Serial.println(distResult);  
}

// 赤外線測距モジュールから読み込む処理
int IDMread(int PinNo) {
     long distA ;
     int i ;
     distA = 0 ;
     for (i=0 ; i < 100; i++) {
          distA  = distA + analogRead(PinNo) ;   // アナログピンから読取る
     } 
     return distA/100 ;                        // 100回の平均値を返す
}

測定した距離を、センチメートルでシリアルモニターに表示するスケッチです。

測定時間が短いので、100回に一回、その平均値を表示します。それでも早すぎるので表示するまで0.1秒のディレイ(休憩時間)を入れています。

シリアルモニターには、このように表示されます。

f:id:solocamptouring:20180526150338p:plain

平らな板ではなく、手帳のざらざらした革表紙を計測してみたため、少しバラツキが大きい結果が表示されました。

GP2Y0E02A まとめ

室内で、センサーと平行な関係になることが分かっている平らな壁との距離を、早く正確に測ることができます。4~50cmの測定範囲が用途にあえば、小型で使いやすいモジュールです。

実用では、衣類などに対するバラツキ幅を乱数として有効活用して、1/fゆらぎを発生するUSB扇風機を作ってみました。

www.solocamptouring.com

 

超音波モジュール HC-SR04

Arduinoで距離を測る場合に、よく使われるモジュールです。

45×20×15mmと少し大柄ですが、超音波発信部と受信部の作りがロボットの目のようで愛嬌がある形です。

データシートでは15°の範囲で2~400cmの距離を測れることになっていますが、相手物がしっかり音を反射して返すかどうかで、計測精度や検知可否が変わってきます。

他にも試して気付いたことを、纏めてみます。

  • 近くの測定は、そこそこ早くて正確。遠くの測定は少し時間がかかる
  • 測定できる相手物は、近くは真正面から左右7.5°の範囲だが、遠くになると正面だけ
  • 相手物にセンサーと平行の超音波反射面がある方が、測定精度が良い
  • 検知物が90°のカドだと45°方向からは計測できない

超音波は思いのほか拡散して薄まるのか、遠くまでしっかり計測できるのは正面方向だけのようです。

とはいえ、1m以内の近距離のものは赤外線センサーよりも安定して計測できるので、こちらの方が使いやすそうです。

ArduinoとHC-SR04の配線図

Arduinoと超音波モジュールHC-SR04の回路図
Arduinoと超音波モジュールHC-SR04の回路

超音波モジュールHC-SR04には、4本の端子があります。

  1. VCC(5V電源)
  2. Trig(トリガー)
  3. Echo(エコー)
  4. GND

1番と4番はArduinoの5VとGNDに接続します。

2番のTrigは、Arduinoから10mSのパルスを入れることで、超音波モジュールに8x40KHzの超音波を発するように指示します。今回はD7に接続します。

3番のEchoは、超音波が返ってくるまでの間ONになります。この時間をArduinoで読むことで、距離を計測できます。今回はD8に接続します。

HC-SR04を使うスケッチ(プログラム)

int trigPin = 7; // trigピンはARDUINOのD7に接続
int echoPin = 8; // echoピンはARDUINOのD8に接続
// defines variables
long duration;
long distance;
long distA ;
long distResult;
int i ;

void setup() {
pinMode(trigPin, OUTPUT); 
pinMode(echoPin, INPUT);
Serial.begin(9600);
}
void loop() {

distA = 0 ;
    for (i=0 ; i < 10; i++) {
 
    digitalWrite(trigPin, LOW); // 初期化
    delayMicroseconds(2);

    digitalWrite(trigPin, HIGH); // 10mSのトリガーをモジュールに出す
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    duration = pulseIn(echoPin, HIGH);// echoピンから帰ってくる数値mSを読む
    distance= duration*0.034/2;// 距離計算
    distA  = distA + distance ; 
    } 
distResult = distA/10 ;   // 10回の平均値をアウトプット

Serial.println(distResult);
}

赤外線モジュール同様に、測定した距離をセンチメートルでシリアルモニターに表示します。

超音波モジュールは遠くまで測定するのに時間がかかるので、100回ではなく10回に一回、その平均値を表示します。

シリアルモニターの表示です。

f:id:solocamptouring:20180526184839p:plain

HC-SR04 まとめ

シリアルモニターの表示でもわかるとおり、近距離の測定結果が赤外線モジュールよりも安定しています。

少し角度がついている面でも、実際よりは遠い距離だと認識しますが計測結果は得られます。 

この超音波モジュールを数個使って自動走行ロボットを制作中です。まだ粗削りの動きなのでレベルアップに挑戦し続けており、製作記事は順次更新いたします。

www.solocamptouring.com

 

LEDサウンドレベルメーターの自作。RGB LEDのWS2812Bをナイトライダー風に光らせる

LEDサウンドレベルメーターの自作。WS2812Bをナイトライダー風に光らせる

ナイトライダーのK.I.T.T.をご存知ですか?

私と同年代の方々は、頭の中でテーマソングの演奏が始まってしまったのではないでしょうか。

おさらいしておくと、ナイトライダーは80年代のアメリカのドラマで、主人公マイケル.ナイトと彼に仕える人工知能を搭載した車の話です。

K.I.T.T.は人工知能の名前で「キット」と呼ばれていました。調べてみてわかったのですが、Knight Industries Two Thousand(ナイト2000)の頭文字をとったものだったそうです。

今回再現するのは、キットが喋る時に連動するボイスインジケータ の光り方だけで、ディテールにはこだわりません。

ドラマでは「了解です、マイケル」などたくさん喋っていましたが、私は運転していて話しかけられても照れるので、キットの声は再現しません。

かわりに、車内の音の大きさに反応するようにして、音楽や人の声にあわせて光るようにします。

市販品があった!

 今回Arduinoで自作しようと決意する前に、市販品で同じようなものがないか探してみたところ、過去に販売されていました。

シガーソケットに差し込むタイプのUSB電源(2個)ですが、いくつかのパターンで喋るようです。

しかし、もう生産されていないようで、日本でもアメリカでもオークションサイトで高値で取引きされています。

簡単に入手できれば、ボックスだけ使って光り方はArduinoで制御したかったのですが、残念です。

Arduinoと接続するモジュール

 ボイスインジケータ を再現するにあたり、WS2812Bを8x8個並べたカラーLEDモジュールと、サウンドセンサーモジュールを使います。

LEDはバーグラフタイプのモジュールの方が実物に近いのですが、ちょうどいい大きさのものが見つからなかったので、使いやすいWS2812Bで進めます。

 サウンドセンサーで検知した音の大きさに応じて、キットのように上下方向にグラフを立てて、カラーLEDモジュールに表示します。

 表示イメージとパターン

 8x8マトリックスLEDのうち6x8個を使って、ボイスインジケータを再現します。

 実物同様の赤色だけで光るパターンの他に、他の色で光ったり、余っている2x8個を違う光り方で装飾したりするパターンにも切り替えられるようにします。

パターン① KITTを再現します(赤色)。

ナイト2000のボイスインジケータをArduinoとWS2812Bで再現

パターン② KITT風で、色を徐々に変化させます。

ナイト2000のボイスインジケータをArduinoとWS2812Bで再現

パターン③ パターン①に加え、3本のインジケーターの間の余っているスペースに光を流します。今回は上下ですが左右に流れる光こそナイトライダーのイメージそのものですね。

パターン④ パターン②に加え、3本のインジケーターの間の余っているスペースに光を流します。

f:id:solocamptouring:20180408122511j:plain
f:id:solocamptouring:20180408122530j:plain

パターン⑤ KARRも再現します(本当はアンバーですが、私の趣味で緑色)。

パターン⑥ KARR風で、色を徐々に変化させます。

KARRのインジケータをArduinoとWS2812Bで再現
パターン⑤(左)と、パターン⑥(右)

パターン⑦ パターン⑤に加え、3本のインジケーターの間の余っているスペースに光を流します。

f:id:solocamptouring:20180408123033j:plain
f:id:solocamptouring:20180408123048j:plain

 

回路図

ナイト2000のボイスインジケータをArduinoとWS2812Bで再現

Fritzingで回路図を作成しました。

マイクモジュールは、MAX4466を搭載したものを使っています。

可変抵抗(ボリューム)を使い、表示7パターンを切り替えられるようにします。

スケッチ(プログラム)は、記事の最後に掲載しておきます。

完成しました!

 まずブレッドボード上で回路を組んで、ipodタッチで出した音をマイクで拾いながら、マイク感度とLED残像調整をしてスケッチを完成させました。

ナイト2000のボイスインジケータをArduinoとWS2812Bで作成

 

完成後の作動状況は動画で確認できます。

7種類の光り方だけ見たい場合は、2:37くらいから確認してみてください。


Arduino project #3 「サウンドレベルメーターをWS2812Bでつくる」Sound level meter with WS2812B

 

 

今回作ったものは、車のオーバーヘッドコンソールに組み込んで光らせますが、その場所には既に、以前の記事で紹介した8x8LEDが搭載されていて、レインボーカラーなどで光っています。

www.solocamptouring.com

 今回のナイトライダー化は、既に搭載されているArduinoにマイクモジュールを追加して、スケッチ(プログラム)を書き換えるだけなので、車での作業はあっという間に終了しました。

f:id:solocamptouring:20180415185857j:plain

スケッチを紹介します

簡単なスケッチ(プログラム)で思い通りの光らせ方ができるGitHubのFastLEDライブラリを使います。

LED光の残像効果は、スケッチ内のfadeToBlackBy()の部分で調整しています。

#include "FastLED.h"
#define DATA_PIN    4
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define HEIGHT 8
#define WIDTH 8
#define NUM_LEDS HEIGHT*WIDTH
CRGB leds[NUM_LEDS];
#define BRIGHTNESS  3

const int sampleWindow = 20; // Sample window width in mS 
unsigned int sample;
int volts;
int micPin = 2;
int selectPin = 5;
int val = 0;
int numLedsToLight = 0;
int numLedsSide = 0;

void setup() {
  delay(3000); //delay for recovery
  FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS); 
}

// List of patterns to cycle through.  Each is defined as a separate function below.
typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { kittOriginal,  kittColor, kittOriginalWithSinelon,  kittColorWithSinelon,  karrOriginal,  karrColor,   karrOriginalWithSinelon };

uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 96; // rotating "base color" used by many of the patterns
  
void loop()
{
   unsigned long startMillis= millis();  // Start of sample window
   unsigned int peakToPeak = 0;   // peak-to-peak level
   unsigned int signalMax = 0;
   unsigned int signalMin = 1024;
 
   // collect data for a while
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(micPin);
      if (sample < 1024)
      {
         if (sample > signalMax)
         {
            signalMax = sample;  // save the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;  // save the min levels
         }
      }
   }
   peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
   volts = (peakToPeak*5/130); // adjusting to the output of the mic 


 val = analogRead(selectPin); //check the volume to choose the pattern
      if (val < 150) {
    gCurrentPatternNumber = 0;
       } else if (val < 300){
    gCurrentPatternNumber = 1;
       } else if (val < 450){
    gCurrentPatternNumber = 2;
       } else if (val < 600){
    gCurrentPatternNumber = 3;
       } else if (val < 750){
    gCurrentPatternNumber = 4;
       } else if (val < 900){
    gCurrentPatternNumber = 5;
       } else {
    gCurrentPatternNumber = 6;      
       }

 
  gPatterns[gCurrentPatternNumber](); // Call the current pattern function once, updating the 'leds' array
  FastLED.show();  // send the 'leds' array out to the actual LED strip

  EVERY_N_MILLISECONDS( 300 ) { gHue++; } // slowly cycle the "base color" through the rainbow
}

void sinelon()
{
  fadeToBlackBy( leds, NUM_LEDS, 8);
  int pos = beatsin16(13,16,23);
  leds[pos] += CHSV( gHue, 255, 192);
}
void sinelonTwo()
{
  fadeToBlackBy( leds, NUM_LEDS, 8);
  int pos = beatsin16(13,40,47);
  leds[pos] += CHSV( gHue, 255, 192);
}

void onecolorsinelon()
{
  fadeToBlackBy( leds, NUM_LEDS, 8);
  int pos = beatsin16(13,16,23);
  leds[pos] += CRGB::MidnightBlue;
}

void onecolorsinelonTwo()
{
  fadeToBlackBy( leds, NUM_LEDS, 8);
  int pos = beatsin16(13,40,47);
  leds[pos] += CRGB::MidnightBlue;
}

void kittOriginal() 
{
       if(volts < 4){
        numLedsToLight = volts; 
          if(volts < 1){
          numLedsSide = 0; 
          }else{
          numLedsSide = numLedsToLight-1; 
          }
       }else{
        numLedsToLight = 4;
        numLedsSide = 3;      
       }
        fadeToBlackBy( leds, NUM_LEDS, 20);
        for(int led = 4-numLedsSide; led < 4; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 4; led < 4+numLedsSide; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 12-numLedsSide; led < 12; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 12; led < 12+numLedsSide; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 28-numLedsToLight; led < 28; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 28; led < 28+numLedsToLight; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 36-numLedsToLight; led < 36; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 36; led < 36+numLedsToLight; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 52-numLedsSide; led < 52; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 52; led < 52+numLedsSide; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 60-numLedsSide; led < 60; led++) { 
            leds[led] += CRGB::Red; 
        }
        for(int led = 60; led < 60+numLedsSide; led++) { 
            leds[led] += CRGB::Red; 
        }
}

void kittColor() 
{
       if(volts < 4){
        numLedsToLight = volts; 
          if(volts < 1){
          numLedsSide = 0; 
          }else{
          numLedsSide = numLedsToLight-1; 
          }
       }else{
        numLedsToLight = 4;
        numLedsSide = 3;      
       }
        fadeToBlackBy( leds, NUM_LEDS, 20);
        for(int led = 4-numLedsSide; led < 4; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 4; led < 4+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 12-numLedsSide; led < 12; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 12; led < 12+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 28-numLedsToLight; led < 28; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 28; led < 28+numLedsToLight; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 36-numLedsToLight; led < 36; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 36; led < 36+numLedsToLight; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 52-numLedsSide; led < 52; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 52; led < 52+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 60-numLedsSide; led < 60; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 60; led < 60+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
}

void kittOriginalWithSinelon() 
{
  sinelon() ;
  sinelonTwo() ;
  kittOriginal() ;
}

void kittColorWithSinelon() 
{
  onecolorsinelon() ;
  onecolorsinelonTwo() ;
  kittColor()  ;
}
void karrOriginal() 
{
       if(volts < 4){
        numLedsToLight = volts; 
        numLedsSide = volts;  
        }else{
        numLedsToLight = 4;
        numLedsSide = 4;      
        }
        fadeToBlackBy( leds, NUM_LEDS, 20);
        for(int led = 8-numLedsSide; led < 8; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 0; led < 0+numLedsSide; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 16-numLedsSide; led < 16; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 8; led < 8+numLedsSide; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 28-numLedsToLight; led < 28; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 28; led < 28+numLedsToLight; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 36-numLedsToLight; led < 36; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 36; led < 36+numLedsToLight; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 56-numLedsSide; led < 56; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 48; led < 48+numLedsSide; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 64-numLedsSide; led < 64; led++) { 
            leds[led] += CRGB::Green; 
        }
        for(int led = 56; led < 56+numLedsSide; led++) { 
            leds[led] += CRGB::Green; 
        }
}
void karrColor() 
{
       if(volts < 4){
        numLedsToLight = volts; 
        numLedsSide = volts;  
        }else{
        numLedsToLight = 4;
        numLedsSide = 4;      
        }
        fadeToBlackBy( leds, NUM_LEDS, 20);
        for(int led = 8-numLedsSide; led < 8; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 0; led < 0+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 16-numLedsSide; led < 16; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 8; led < 8+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 28-numLedsToLight; led < 28; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 28; led < 28+numLedsToLight; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 36-numLedsToLight; led < 36; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 36; led < 36+numLedsToLight; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 56-numLedsSide; led < 56; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 48; led < 48+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192); 
        }
        for(int led = 64-numLedsSide; led < 64; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
        for(int led = 56; led < 56+numLedsSide; led++) { 
            leds[led] += CHSV( gHue, 255, 192);
        }
}
void karrOriginalWithSinelon() 
{
  sinelon() ;
  sinelonTwo() ;
  karrOriginal() ;
}