2014/05/30

大眼仔從左至右, 再由右至左連動
熱閙的 Maker Faire 過後, 冷卻了幾天, 是時候讓大眼仔進化了.

萊恩大兵把大眼仔的程式碼包成 class 與 library, 從 Arduino 寫程式操控這些小傢伙們會更直覺一點. 

程式下載.

 
/*
  AniEyeball.h - Library for Openlab Taipei Animatronic Eveball.
  Created by Ryan Tseng, May 27, 2014.
  Released into the public domain.
*/

#ifndef AniEyeball_h
#define AniEyeball_h

#include <Arduino.h>
#include <Servo.h>

class AniEyeball
{
  public:
    AniEyeball();
    AniEyeball(int pinServoP, int pinServoB);
    void setPinP(int pinP);
    void setPinB(int pinB);
    void setPPos(int msec);
    void setBPos(int msec);
    void acting(int n);
    void mix();
    void center();
    void turnLeftSlowly();
    void turnRightSlowly();
    void turnLeftBlinking();    
    void turnRightBlinking();    
    void guarding();
    void sleepy();
  private:
    Servo _servoP;
    Servo _servoB;
    int _pinServoP;
    int _pinServoB;
};
#endif


Class: AniEyeball
- 它內含兩個 Servo, 分別是眼皮(P)與眼球(B).
- 它的 constructor 需要餵 servo pin# 當參數. 也可以直接宣告不帶任何參數, 稍後再另行設定 servo pin#.
- 它的 method 主要包括有:

  • setPPos(int msec): 設定眼皮的位置. 參數單位是 microseconds.
  • setBPos(int msec): 設定眼球的位置. 參數單位是 microseconds.
  • acting(int n): 指定眼皮眼球做某個預設動作組.
  • 其它turnLeftSlowly()turnRightBlinking(), guarding(), sleepy(): 這些都是預設動作組.
AniEyeball.cpp
 
/*
  AniEyeball.cpp - Library for Openlab Taipei Animatronic Eveball.
  Created by Ryan Tseng, May 27, 2014.
  Released into the public domain.
*/

#include <Arduino.h>
#include "AniEyeball.h"

int posP, posB;

// 眼皮: 
//   - 1000: 全開
//   - 1500: 半開
//   - 2000: 閉眼
// 眼球:
//   - 1000: 左
//   - 1500: 中
//   - 2000: 右

AniEyeball::AniEyeball()
{
}

AniEyeball::AniEyeball(int pinP, int pinB)
{
  _pinServoP = pinP;
  _pinServoB = pinB;
  _servoP.attach(_pinServoP);
  _servoB.attach(_pinServoB);  
}

void AniEyeball::setPinP(int pinP)
{
  _pinServoP = pinP;
  _servoP.attach(_pinServoP);
}

void AniEyeball::setPinB(int pinB)
{
  _pinServoB = pinB;
  _servoB.attach(_pinServoB);
}

void AniEyeball::setPPos(int msec)
{
  if (msec > 2000) posP = 2000;
  if (msec < 1000) posP = 1000;
  posP = msec;
  
  if (!_servoP.attached()) _servoP.attach(_pinServoP);  
  _servoP.writeMicroseconds(posP);
  delay(15);      

  //_servoP.detach();
}

void AniEyeball::setBPos(int msec)
{
  if (msec > 2000) posB = 2000;
  if (msec < 1000) posB = 1000;
  posB = msec;
  
  if (!_servoB.attached()) _servoB.attach(_pinServoB);  
  _servoB.writeMicroseconds(posB);
  delay(15);      

  //_servoB.detach();
}

void AniEyeball::acting(int n)
{
  switch (n)
  {
    case 1:
      turnLeftSlowly();
      break;
    case 2:
      turnRightSlowly();
      break;
    case 3:
      turnLeftBlinking();
      break;
    case 4:
      turnRightBlinking();
      break;
    case 5:
      guarding();
      break;
    case 6:
      sleepy();
      break;
    case 7:
      mix();
      break;
    default:
      break;
  }
}

void AniEyeball::center()
{
  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  posP = posB = 1500;

  _servoP.writeMicroseconds(posP);  
  _servoB.writeMicroseconds(posB);
  delay(15);      

  _servoP.detach();
  _servoB.detach();
}

void AniEyeball::turnLeftSlowly()
{
  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  posP = 1000;
  posB = 1800;
  
  _servoP.writeMicroseconds(posP);  
  _servoB.writeMicroseconds(posB);
  delay(1000);
  
  while (posB >= 1000)
  {
    _servoB.writeMicroseconds(posB);  
    posB -= 200;
    delay(100);
  } 

//  delay(3000);

  _servoP.detach();
  _servoB.detach();  
}

void AniEyeball::turnRightSlowly()
{
  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  posP = 1000;
  posB = 1200;
  
  _servoP.writeMicroseconds(posP);  
  _servoB.writeMicroseconds(posB);
  delay(1000);
  
  while (posB <= 2000)
  {
    _servoB.writeMicroseconds(posB);  
    posB  = 200;
    delay(100);
  } 

//  delay(3000);

  _servoP.detach();
  _servoB.detach();  
}

void AniEyeball::turnLeftBlinking()
{
  // 眼皮: 
  //   - 閉 -&gt 全開, 瞬開
  // 眼球
  //   - 中 -&gt 左, 慢慢
  // 眼皮:
  //   - 全開 -&gt 閉, 瞬閉

  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  _servoP.writeMicroseconds(2000);
  delay(1000);

  _servoP.writeMicroseconds(1000);
  delay(1000);

  posB = 1700;
  _servoB.writeMicroseconds(posB);
  delay(500);

  while (posB > 1000)
  {
    posB -= 50;
    _servoB.writeMicroseconds(posB);
    delay(30);
  }
  delay(3000);

  _servoP.writeMicroseconds(2000);
//  delay(1000);

  _servoP.detach();
  _servoB.detach();    
}

void AniEyeball::turnRightBlinking()
{
  // 眼皮: 
  //   - 閉 -&gt 全開, 瞬開
  // 眼球
  //   - 中 -&gt 右, 慢慢
  // 眼皮:
  //   - 全開 -&gt 閉, 瞬閉

  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  _servoP.writeMicroseconds(2000);
  delay(1000);

  _servoP.writeMicroseconds(1000);
  delay(1000);

  posB = 1300;
  _servoB.writeMicroseconds(posB);
  delay(500);

  while (posB < 2000)
  {
    posB  = 50;
    _servoB.writeMicroseconds(posB);
    delay(30);
  }
  delay(3000);

  _servoP.writeMicroseconds(2000);
//  delay(1000);

  _servoP.detach();
  _servoB.detach();      
}

void AniEyeball::guarding()
{
  // 眼皮: 
  //   - 閉 -&gt 全開, 瞬開
  // 眼球
  //   - 中+ -&gt 左 -> 右, 瞬轉
  // 眼皮:
  //   - 全開 -&gt -半閉, 瞬閉
  //   - 重覆 2 遍

  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  _servoP.writeMicroseconds(2000);
  _servoB.writeMicroseconds(1800);
  delay(1000);

  _servoP.writeMicroseconds(1000);
  _servoB.writeMicroseconds(1000);
  delay(500);

  _servoB.writeMicroseconds(2000);

  for (int i=0; i<2; i++)
  {
    _servoP.writeMicroseconds(1800);
    delay(500);  

    _servoP.writeMicroseconds(1000);
    delay(500);
  }  
//  delay(1000);  

  _servoP.detach();
  _servoB.detach();        
}

void AniEyeball::sleepy()
{
  // 眼皮: 
  //   - 閉 -&gt +半開, 慢慢張開
  //   - +半開 -&gt 閉, 瞬閉
  //   - 重覆 3 遍

  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  posP = 2000;

  for (int i=0; i<3; i++)
  {
    while (posP >= 1200)
    { 
      _servoP.writeMicroseconds(posP);
      posP -= 50;
      delay(100);
    }
    delay(1000);
    posP = 2000;
    _servoP.writeMicroseconds(posP);  
    delay(1000);
  }
 // delay(1000);

  _servoP.detach();
  _servoB.detach();          
}

void AniEyeball::mix()
{
  if (!_servoP.attached()) _servoP.attach(_pinServoP);
  if (!_servoB.attached()) _servoB.attach(_pinServoB);

  posP = 2000;
  posB = 1500;

  _servoP.writeMicroseconds(posP);
  _servoB.writeMicroseconds(posB);  
  delay(15);

  // 眼皮: 
  //   - 閉 -&gt 半開, 慢慢張開
  while (posP >= 1300)
  {
    _servoP.writeMicroseconds(posP);
    posP -= 50;
    delay(100);
  }

  // 眼球: 
  //   - 中 -&gt 右, 慢慢轉
  //   - 右 -&gt 左, 快轉
  //   - 左 -&gt 中, 慢慢轉
  while (posB <= 2000)
  {
    _servoB.writeMicroseconds(posB);
    posB  = 50;
    delay(100);
  }  

  while (posB >= 1000)
  {
    _servoB.writeMicroseconds(posB);
    posB -= 150;
    delay(30);
  }  

  while (posB <= 1500)
  {
    _servoB.writeMicroseconds(posB);
    posB  = 50;
    delay(100);
  } 

  // 眼皮: 
  //   - 半開 -&gt 全開, 慢慢張開
  //   - 全開 -&gt 閉, 快快閉
  //   - 閉 -&gt 全開, 快快開
  posP = 1700;
  while (posP >= 1000)
  {
    _servoP.writeMicroseconds(posP);
    posP -= 50;
    delay(100);
  }  

  while (posP <= 2000)
  {
    _servoP.writeMicroseconds(posP);
    posP  = 150;
    delay(30);
  } 

  while (posP >= 1000)
  {
    _servoP.writeMicroseconds(posP);
    posP -= 150;
    delay(30);
  }   

 // delay(1000);

  _servoP.detach();
  _servoB.detach();            
}


在 Arduino 端, 直接宣告 AniEyeball 陣列, 程式碼變得乾淨多了.

 
#include <Servo.h>
#include <AniEyeball.h>

#define N_EYEBALL 4

AniEyeball ot108eyeball[N_EYEBALL];

// 眼皮_PIN # i
// 眼球_PIN # i+1
//const byte servoPin[N_EYEBALL*2] = {2,3,4,5,6,7,8,9,10,11,12,13};
const byte servoPin[N_EYEBALL*2] = {2,3,4,5,6,7,8,9};

void setup()
{
  Serial.begin(9600);

  for (int i=0; i<N_EYEBALL; i++)
  {
   ot108eyeball[i].setPinP(servoPin[i*2]);
   ot108eyeball[i].setPinB(servoPin[i*2+1]);
  }
}

void loop()
{
  for (int i=0; i<N_EYEBALL; i++)
    ot108eyeball[i].setPPos(1000); // 眼皮全開
  delay(500);

  for (int pos=1000; pos<=2000; pos+=250)
  {
    for (int i=0; i<N_EYEBALL; i++)
    {
      ot108eyeball[i].setBPos(pos); // 眼球 左-&gt右
      delay(150);
    }
  }
  delay(500);
    
  for (int pos=2000; pos>=1000; pos-=250)
  {
    for (int i=N_EYEBALL-1; i>=0; i--)
    {
      ot108eyeball[i].setBPos(pos); // 眼球 右-&gt左
      delay(150);
    }
  }
  delay(500);

  for (int pos=1000; pos<=2000; pos+=500)
  {
    for (int i=0; i<N_EYEBALL; i++)
    {
      ot108eyeball[i].setBPos(pos); // 眼球 左-&gt右
      delay(150);
    }
  }
  delay(500);
    
  for (int pos=2000; pos>=1000; pos-=500)
  {
    for (int i=N_EYEBALL-1; i>=0; i--)
    {
      ot108eyeball[i].setBPos(pos); // 眼球 右-&gt左
      delay(150);
    }
  }
  delay(500);

  for (int i=0; i<N_EYEBALL; i++)
    ot108eyeball[i].setPPos(2000); // 眼皮全閉
  delay(500);
}

實際執行... 看來效果還不錯.

嗯.. 發現一個問題. 大眼仔彼此是獨立的, 一次一個輪流動作如果動作的久一點, 其它的大眼仔會處在 idle 狀態, 少了群動效果. 

要怎麼做到群動效果呢? 萊恩大兵的解法是, 將兩個(或更多)的大眼仔接在同一腳位上. 例如:
AnyEyeball1 -> PIN #2, #3 
AnyEyeball2 -> PIN #4, #5
AnyEyeball3 -> PIN #6, #7
AnyEyeball4 -> PIN #6, #7
藉由麵包板把兩隻大眼仔串在一起.
這樣接之後, 就會是大眼仔1/2/3 各自獨立動作, 且大眼仔3與4是連動的.
左邊兩隻是連動的, 和右邊不同動作.


先到此, 再下來就是多片 arduino 板子之間的連動了.


[萊恩大兵的其它文章]

自製大四軸

自製大四軸, 實作分享@華山文創文區
自製大四軸, (1) 零組件篇, 遙控器 (Drone, Quadcopter, Futaba, Maker, Arduino, Animatronic Eye)
自製大四軸, (2) 零組件篇, 飛控板 (Drone, Quadcopter, MultiWii, Arduino, Futaba, Maker)
自製大四軸, (3) 零組件篇, 自行雷切木質機架 (Drone, Quadcopter, Maker, Laser Cut)
自製大四軸, (4) 零組件篇, 馬達與電變調整 (Drone, Quadcopter, Maker, Electric Speed Control, Motor)
自製大四軸, (5) 組裝篇, 四軸飛行器成形 (Drone, Quadcopter, MultiWii, Arduino, Maker, Electric Speed Control, Motor)
自製大四軸, (6) 調整篇, 飛行前兩三事 (Drone, Quadcopter, Maker, Futaba, Arduino, MultiWii)
自製大四軸, (7) 充電篇, iMax B6 充電器操作記要 (Charger, Battery)
自製大四軸, (8) 問題篇, 機架損壞維修 (Drone, Quadcopter, Laser Cut)
自製大四軸, (9) 改良篇, 方便拆卸的木質機架 (Drone, Quadcopter, Maker, Laser Cut)

自動報球速的棒球



CC2540 Bluetooth Low Energy
筆記, CC2540 Bluetooth Low Energy, (1) 開發環境 架設 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (2) 跑第一個範例程式 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (3) SimpleBLEPeripheral 簡單介紹 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (4) 在智慧手機上執行範例程式 (Bluetooth, CC2540)
筆記, CC2540 Bluetooth Low Energy, (5) 偵測與發送 iBeacon 訊號 (Bluetooth, CC2540, iBeacon)
實作, iBeacon 發訊器 x 防丟器 (Bluetooth, CC2540, iBeacon)
實作, iBeacon 尋寶遊戲 (Bluetooth, CC2540, iBeacon, iOS app)
實作, BLE + iOS app, 遙控燈泡君 (Bluetooth, CC2540, iOS app)
做實驗, 用 iBeacon 做自動控制的可行性 (Bluetooth, iBeacon, CC2540, Automation, URL Scheme, iOS app)

藍色小鋪一起來做

藍色小鋪一起來做, (1) 用 beacon 控制開關的枱燈
藍色小鋪一起來做, (2) 講解 BLE CC2540 UART 通訊範例程式 (Bluetooth, CC2540, UART)
藍色小鋪一起來做, (3) 藍牙枱燈專案實作 (上) (Bluetooth, CC2540)
藍色小鋪一起來做, (4) 藍牙枱燈專案實作 (下) (Bluetooth, CC2540)
藍色小鋪一起來做, (5) iBeacon scanner 專案示範與解說 (Bluetooth, CC2540, iBeacon)
藍色小鋪一起來做, (6) 完成, 用 iBeacon 控制開關的枱燈 (Bluetooth, CC2540, iBeacon)

小惡魔 無線溫度感測器

108 大眼仔
Plot Clock
體驗, 原住民互動故事書@宜蘭大同鄉泰雅生活館
體驗, 蛋生音互動裝置@兒童美術館 (Arduino, 3D Printing, HC-SR04, Interactive)



實作, 電容感應音樂樹

0 意見:

張貼留言