顯示具有 Arduino 標籤的文章。 顯示所有文章
顯示具有 Arduino 標籤的文章。 顯示所有文章

2022年11月13日 星期日

[科技宝, Cagebot, 8787, AI, Arduino] 國廠祥儀科技寶智慧手臂 3/3:電控程式設篇

 國廠祥儀科技寶智慧手臂 3/3:

電控程式設計篇


Nov. 13, 2022
[1]

  [2][3]

本系列第三篇文章延續了第一篇《國廠祥儀科技寶智慧手臂 1/3:機構設計篇(https://reurl.cc/Wq1aWZ)》的解析,本文將進一步地著墨在控制板(Arduino Nano)PCA9685  8787 電控程式設計上(圖 1 最右方塊)。

圖 1:機電整合專案三大設計

完整的電控程式

我們先來看看將控制板、PCA9685 與 8787 三者整合後的完整程式(https://reurl.cc/585AlM)(註:祥儀的 RD 是以 Performio(https://www.performio.com)這套 IDE 來開發智慧手臂的測試程式的,所以,它的主程式為 main.cpp。我們打開這隻程式細看,setup()、loop() 依舊是 Arduino IDE 的 C/ C++ 語法。因此,只要將智慧手臂的函式庫(https://reurl.cc/58paLz)複製到Arduino IDE 預設的函式庫存放路徑 C:\Program Files (x86)\Arduino\libraries 或 C:\Users\<user>\Documents\Arduino\libraries 之下即可順利編譯之。):

黃色指令為用來預測 8787 移動路徑的卡爾曼濾波器(Kalman filter)的程式碼;
紫色指令為 PCA9685 伺服馬達擴充板的控制程式碼;
綠色指令為 威盛 8787 AI 視覺感測器的控制程式碼。

#include <Arduino.h>
#include <Wire.h>
#include <SoftwareSerial.h>
#include "PCA9685.h"
#include "SimpleKalmanFilter.h"
#include "Pixetto.h"

#define sevo1_pin 0
#define servo1_angle_min -90
#define servo1_angle_max 90
#define servo1_angle_init 0

#define sevo2_pin 1
#define servo2_angle_min -90
#define servo2_angle_max 90
#define servo2_angle_init 0

#define sevo3_pin 2
#define servo3_angle_min -90
#define servo3_angle_max 90
#define servo3_angle_init 0

#define sevo4_pin 3
#define servo4_angle_min -90
#define servo4_angle_max 90
#define servo4_angle_init 0

#define rxPin 11
#define txPin 9

Pixetto ss(rxPin, txPin);

PCA9685 driver;
// PCA9685 輸出 = 12 位 = 4096 步
// 20ms 的 2.5% = 0.5ms ; 20ms 的 12.5% = 2.5ms
// 4096 的 2.5% = 102 步;4096 的 12.5% = 512 步
PCA9685_ServoEval pwmServo1; // (0deg, 90deg, 180deg)
PCA9685_ServoEval pwmServo2; // (0deg, 90deg, 180deg)
PCA9685_ServoEval pwmServo3; // (0deg, 90deg, 180deg)
PCA9685_ServoEval pwmServo4; // (0deg, 90deg, 180deg)

SimpleKalmanFilter KalmanFilter_X(1, 1, 0.01);
SimpleKalmanFilter KalmanFilter_Y(1, 1, 0.01);

int servo1Pos, servo2Pos, servo3Pos; // 當前角度
int KalmanX, KalmanY;

void setup() {
  Wire.begin(); // Wire must be started first

  driver.resetDevices(); // Software resets all PCA9685 devices on Wire line
  driver.init(); // Address pins A5-A0 set to B000000
  driver.setPWMFreqServo(); // Set frequency to 50Hz

  driver.setChannelPWM(0, pwmServo1.pwmForAngle(servo1_angle_init));
  delay(10);
  driver.setChannelPWM(1, pwmServo2.pwmForAngle(servo2_angle_init));
  delay(10);
  driver.setChannelPWM(2, pwmServo3.pwmForAngle(servo3_angle_init));
  delay(10);
  driver.setChannelPWM(3, pwmServo4.pwmForAngle(servo4_angle_init));
  delay(10);

  ss.begin();
  ss.enableFunc(Pixetto::FUNC_COLOR_DETECTION);
  delay(10);
  Serial.begin(9600);
  
  servo1Pos = servo1_angle_init+90;
  servo2Pos = servo2_angle_init+90;
  servo3Pos = servo3_angle_init+90;
}

void loop() {
  if (ss.isDetected()) {
    Serial.print("x:");
    Serial.println(ss.getPosX());
    Serial.print("y:");
    Serial.println(ss.getPosY());
    Serial.print("w:");
    Serial.println(ss.getWidth());
    Serial.println("");
    KalmanX = KalmanFilter_X.updateEstimate(ss.getPosX());
    KalmanY = KalmanFilter_Y.updateEstimate(ss.getPosY());
    if (ss.getFuncID() == Pixetto::FUNC_COLOR_DETECTION) {
      if (ss.getTypeID() == Pixetto::COLOR_RED) {
        if (KalmanX >= 50) {
          servo1Pos -= 5;
          if (servo1Pos >= servo1_angle_max+90)
            servo1Pos = 180;
          driver.setChannelPWM(0, pwmServo1.pwmForAngle(servo1Pos-90));
        }
        else if (KalmanX <= 25) {
          servo1Pos += 5;
          if (servo1Pos <= servo1_angle_min+90)
            servo1Pos = 0;
          driver.setChannelPWM(0, pwmServo1.pwmForAngle(servo1Pos-90));
        }
        else if (KalmanY <= 17) {
          servo2Pos += 5;
          if (servo2Pos >= servo2_angle_max+70)
            servo2Pos = 160;
          driver.setChannelPWM(1, pwmServo2.pwmForAngle(servo2Pos-90));

        }
        else if (KalmanY >= 60) {
          servo2Pos -= 5;
          if (servo2Pos <= servo2_angle_min+90)
            servo2Pos = 0;
          driver.setChannelPWM(1, pwmServo2.pwmForAngle(servo2Pos-90));
        }

        if (servo2Pos <= 65) {
          servo3Pos = servo2Pos + 25;
          if (servo3Pos <= servo3_angle_min+90)
            servo3Pos = 0;
          driver.setChannelPWM(2, pwmServo3.pwmForAngle(servo3Pos-90));
        }
        else if (servo2Pos >= 85) {
          servo3Pos = servo2Pos + 10;
          if (servo3Pos >= servo3_angle_max+90)
            servo3Pos = 180;
          driver.setChannelPWM(2, pwmServo3.pwmForAngle(servo3Pos-90));
        }
        else {
          servo3Pos = servo3_angle_init;
          driver.setChannelPWM(2, pwmServo3.pwmForAngle(servo3Pos));
        }

      }
    }
  }
}

如圖 2 所示,電控程式是用來控制硬體運作的指令集合,因此,我們先要想像智慧手臂要如何動作,然後將之對應到伺服馬達群的一連串控制劇本(script)。例如,我們想要讓智慧手臂追蹤到紅球就伸爪去夾住它,將這段劇本翻譯成伺服馬達控制語言則變成:左右轉動伺服馬達 1 去追紅球,水平定位到之後再移動伺服馬達 3 重直定位到紅球蹤跡。接著,調整伺服馬達 2 調整紅球和 8787 遠近的深度(depth)。三軸鎖定紅球的位置後即可張爪夾球。

圖 2:電控程式學習拆解

圖 2 中最右邊的淺藍色方塊表示現今流行的伺服馬達控制程式界面概可分為積木程式、C/ C++ 和 MicroPython 的語法程式。
關於智慧手臂如何自動追蹤紅球,在 Notepad++ 文字編輯器中,我們將上述的程式碼打開,就能很清楚的看到圖 2 中紅框內所框選的即為實作的程式碼,它判斷的數據如圖 3 上半部圖示。此外,在追蹤移動時,為了讓 8787 永遠保持置中,圖 2 中的綠框處即為實作的程式碼,它的判斷數據如圖 3 的最下方圖示。

圖 2:自動追蹤的程式片斷

圖 3:自動追蹤紅球時 8787 的移動位置調整

電控程式之主控板及開發環境建置

智慧手臂的控制板是 Arduino Nano 的相容板,裝妥 CH340 驅動程式、從裝置管理員確認它被指派到的 COM 埠編號後,我們就可以使用 Arduino IDE (我們使用 V1.8.9(https://reurl.cc/06Xq3Y)版本,祥儀 RD 建議以 V1.8.X 版為佳──經我們測試,最新的 V2.0.x 版改幅度不小,不建議讀者們採用)來開啟祥儀研發的範例程式。其中,Board 設定為 Arduino Nano、Processor 設定為 ATmega32p (Old Bootloader)、Port 設定為裝置管理員中列出 CH34 的 COM 埠編號。
萬事皆備,我們就可以將 Arduino 界最著名的 Blink 範例程式(讓板載在 D13 腳位上的 SMD LED 閃爍)上傳到控制板來測試我們的開發環境是否全然準備妥當,如圖 4 所示。我們將 Blink 的程式碼摘列如下:

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

圖 4:控制板在 Arduino IDE V.1.8.9 的設定

電控程式之 PCA9685

為了能準確的控制每一顆伺服馬達,我們應該要先弄清楚一顆伺服馬達是如何以指令程式達成控制的。從 Github 上取得 PCA9685 的控制程式碼 pwmtest.ino https://reurl.cc/06XNMo,我們將之調整成「單顆」伺服馬達的測試程式如下:

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVOMIN  150
#define SERVOMAX  600
#define USMIN  600
#define USMAX  2400
#define SERVO_FREQ 50

uint8_t servonum = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("8 channel Servo test!");

  pwm.begin();
 
  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(SERVO_FREQ);

  delay(10);
}

void setServoPulse(uint8_t n, double pulse) {
  double pulselength;
  
  pulselength = 1000000;
  pulselength /= SERVO_FREQ;
  Serial.print(pulselength); Serial.println(" us per period"); 
  pulselength /= 4096;
  Serial.print(pulselength); Serial.println(" us per bit"); 
  pulse *= 1000000;
  pulse /= pulselength;
  Serial.println(pulse);
  pwm.setPWM(n, 0, pulse);
}

void loop() {
  Serial.println(servonum);
  for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);
  for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);

  for (uint16_t microsec = USMIN; microsec < USMAX; microsec++) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);
  for (uint16_t microsec = USMAX; microsec > USMIN; microsec--) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);

  if (servonum > 7) servonum = 0; // Testing the first 8 servo channels
}

現在,我們已經學會了單顆伺服馬達了,接下來就可以輕鬆地把像圖 2 提及的智慧手臂動作劇本轉換成對應的四顆伺服馬達控制指令了,讀者們可以自行挑戰看看!

此外,卡爾曼濾波器的測試程式可從此 https://reurl.cc/X5V05M 下載測試並從中了解這個數學模型的使用方式。

電控程式之 8787

首先,在 Pixtto 公用程式(Pixetto Utility)中設定使用 8787 內建的顏色偵測(color detection) AI 模型,如圖 5 所示。

圖 5:在 Pixetto 公用程式中指定顏色偵測的內建 AI 功能

緊接著,因為智慧手臂的 Github(https://reurl.cc/58paLz)中已含有 8787 V1.5.1 版的 Arduino 函式庫,所以,在 Arduino IDE 中可以使用下列的語法架構如來設計:

if (ss.isDetected()) {
    if (ss.getFuncID() == Pixetto::FUNC_COLOR_DETECTION) {
        if (ss.getTypeID() == Pixetto::COLOR_RED) {
        //...
    }
}
}

關於 8787 內建的 AI 模型詳列如下,有興趣的讀者們可詳參 https://reurl.cc/qZN7Mp 這隻 C 語言的標頭檔(header file):

enum EFunc
{
FUNC_COLOR_DETECTION = 1,
FUNC_COLOR_CODE_DETECTION = 2,
FUNC_SHAPE_DETECTION = 3,
FUNC_SPHERE_DETECTION = 4,
FUNC_TEMPLATE_MATCHING = 6,
FUNC_KEYPOINTS = 8,
FUNC_NEURAL_NETWORK = 9,
FUNC_APRILTAG = 10,
FUNC_FACE_DETECTION = 11,
FUNC_TRAFFIC_SIGN_DETECTION = 12,
FUNC_HANDWRITTEN_DIGITS_DETECTION = 13,
FUNC_HANDWRITTEN_LETTERS_DETECTION = 14,
FUNC_CLOUD_DETECTION = 15,
FUNC_LANES_DETECTION = 16,
FUNC_EQUATION_DETECTION = 17,
FUNC_SIMPLE_CLASSIFIER = 18,
FUNC_VOICE_COMMAND = 19
};

[科技宝, Cagebot, 8787, AI, Arduino] 國廠祥儀科技寶智慧手臂 2/3:機電整合設計篇

 國廠祥儀科技寶智慧手臂 2/3:

機電整合設計篇


Nov. 13, 2022
[1]

 [2][3]

本系列第二篇文章延續了前一篇《國廠祥儀科技寶智慧手臂 1/3:機構設計篇(https://reurl.cc/Wq1aWZ)》的解析,本文將進一步地著墨在機電整合設計上(圖 1 中間方塊),仔細地剖析控制板(Arduino Nano)伺服馬達 16 路伺馬達擴充板 PCA9685 的電路設計。這三者的接線圖如圖 3,電路圖如圖 4 所示。註:智慧手臂的控制板在出廠的接線已改為以 D11(8787 Grove 的黃線接做為接收 Rx 的控制板接腳) 和 D9(8787 Grove 的白線接做為傳送 Tx 的控制板接腳) 和 8787 做四線對接交握(handshaking)通信,請讀者自行調整圖 2 和圖 3 的接腳接線。 

圖 1:機電整合專案三大設計

圖 2:智慧手臂接線圖

圖 3:智慧手臂電路圖

另一方面,因為伺服馬達與 8787 都是吃大電流的電子元件,以「多電源共地」方式拉電源是比較安穩的供電方式(圖 2 是以二顆 18650 的 7.4 V 電池並聯供電) [4]:

電源供應 1:供應 5V 給 PCA 9685 去控四顆伺服馬達
電源供應 2:供應 5V 給 8787
電源供應 3:供應 5V 給控制板 

機電整合之 Arduino Nano

智慧手臂使用祥儀自家設計的科技寶(Cagebot)控制板(https://reurl.cc/X5jn1a),這是一塊 Arduino Nano 的相容板子,使用前要先掛上 CH340 驅動程式(https://reurl.cc/VRDVK6)。然後在裝置管理員(device manager)(以 Windows 為例,其他作業系統請自行尋找)中可查看到作業系統所指派的序列埠(COM port)編號──此號碼很重要,一定要學會隨時查看,因為我們隨後會以 Arduino IDE 開發工具透過此通道將程式上傳到控制板中。

機電整合之伺服馬達

伺服馬達採用了閉迴路(closed-loop)迴授(feedback)機制(https://reurl.cc/aaV0vZ),使之能以 0~180 度控制其轉動角度。至於馬達內的各部件(parts),強烈建議讀者以破壞式學習(https://reurl.cc/aaV0vZ),透過逐一拆解的過程去印證這些組成部件的關連性,以建立知識的直覺(intutition)

機電整合之 PCA9685

(伺服)馬逹是以電生磁,磁生力的電磁作用轉動的,它在啟動瞬間需要較大的啟動電流去推動,因此,切勿貪圖一時方便直接將之接到控制板的 GPIO(General Purpose Input/ Output)接腳(pin)上。雖然偶一為之看不出它對控制板的傷害,但隨著多次的積累使用,其實是在加速控制板的老化(burnout),就好像一直往存款有限的戶頭大量提款,這筆存款就會很「快」地被提領殆盡的。因此,我們選擇了 16 路四線 I2C 匯流排(bus)介面的馬達擴充板 PCA9685(https://reurl.cc/58pVn7)來趨動智慧手臂的四軸。

機電整合之 8787

8787 以 Grove 四線(Tx 黃、Rx 白、+5V 紅、GND 黑)和控制板的 D11(Rx)與 D9(Tx)透過軟體序列(software serial)進行交握式通信。兩者間連接方式如圖 5 所示。其中,8787 的 micro USB 接頭可供影像輸出校正監控之用。強烈建議元成校正後請保持 8787 獨立供電,以確保它能正常工作。

圖 5:8787 接線圖

2022年11月5日 星期六

[科技宝, Cagebot, 8787, AI, Arduino] 國廠祥儀科技寶智慧手臂 1/3:機構設計篇

 國廠祥儀科技寶智慧手臂 1/3:

機構設計篇


Nov. 5, 2022
[1]

  [2][3]

當國產祥儀塑鋼積木手臂添加了威盛(VIA)的 Pixetto(文後皆以 8787 稱之)AI 視覺感測器(vision sensor)(文後皆以智慧手臂稱之)會產生什麼綜效?本系列文章將從機構與機電整合設計兩面向逐一解說這其中的秘辛。

套件學習法

廠商都幫忙把機構、機電整合、電控程式、教材包成懶人包了,剩下的就只是按圖施工(腦袋空空)了嗎?
一般而言,知識、技能的學習約莫有兩種方式:由上而下(top down)和由下而上(bottom up),如圖 1 所示。其中,因前者已經有了套件典範(paradigm),所以我們比較容易將之拆解(decompose)而學習,但會因結果已知而限制了創意的擴散思考設計。然而,從基礎的各個知識點按部就班依序學習完畢後才將之整合(integrate)而的設計亦是另有一番風景。不過,見樹不見林的盲點容易導致學完新的知識就忘了舊的。

圖 1:兩學習方法論

智慧手臂教學採由上而下方式將大部頭的知識點逐一拆解至容易理解的各個小塊知識基元來還原作品設計的各階段歷程,此套件學習法如圖 2 所示。我們期待學員們能以設計師(designer)的視野去綜覽 why、how、…等等緊密的設計思維來欣賞這隻智慧手臂的創造歷程,並經由親手仿作來體驗與學習。

圖 2:套件學習法

仿生

舉起您的手臂活動活動,覺察一下:圖 3 的 1 處關節(joint)可以 360 度運動,2、3 處只能在限定角度下運動,而 4 處可做抓取。

圖 3:人體手臂四關節

圖 4 的智慧手臂設計即是模仿人類的手臂運動,在 1~4 的關節點放上可以帶動的四顆伺服(servo)馬達(SG90 和 MG995)而使得它有四軸(axies)可以在空間中自由的運動。其中,伺服馬達 1 可 0~180 度左右轉動,伺服馬達 2、3 負責控制大、小手臂的上下運動,伺服馬達 4 則控制抓子的開合抓取。

圖 4:智慧手臂四軸

參考設計:MeArm

筆者在約莫數年前即接觸了著名的機器手臂開源(open source)專案 MeArm(https://reurl.cc/oZ1ArV),它是由底盤、大手臂、小手臂和夾子四段所組成(https://reurl.cc/QWLGNZ),

模仿人手動作的秘密在於四個馬達,前方馬達控制爪子的開合;左左兩個馬達用來控制手臂前後伸展及上下移動;安置於下方的馬達是讓整組機身左右旋轉。 ~https://reurl.cc/gQ212N

請讀者們自行比較智慧手臂和它運動方式的異同之處。

機構組裝

根據祥儀提供的第一手組裝指引,我們將之轉檔成圖 5 的影片供讀者們參考。我們強烈建議讀者們根據圖 4 所標示的軸編號,依照數字順序組裝時,每裝妥一軸即輕輕(太大力轉動會使伺服馬達齒輪損壞)轉動觀察它們是如何動作的─對比於人的手臂和 MeArm 設計的異同點為何?

圖 5:智慧手臂組裝

開光:智慧視覺感測器(smart vision sensor)

如圖 6 所示的 demo 影片,智慧手臂使用了 8787 內建的顏色偵測(color detection) AI 模型,並實現了有色物件自動追蹤的示例。機電整合及電控程式的分析將以後文詳述之。
關於 8787 一系列的介紹文章,可以參考筆者所著的「假 a 真 i(https://reurl.cc/28ZV5a)」專網。

圖 6:有色物件自動追蹤手臂

生活中的機器手臂應用

處在這個科技爆發的時代,我們更期待著新技術能從自動化進入到智慧化,乃至最後可以達到無人化的全新應用領域。像是從工廠自動化自動物流智慧工廠,到無人咖啡店,再到藝術創作或者是手術機器人,智慧手臂開始有了更進一步的生活應用。然而智慧手臂的設計還是從這些基礎學習開始的。因此,不管是由上而下的套件學習法或者是由下而上的按部就班學習法,找到自己的學習興趣仍是自我學習的重要任務──喜歡智慧手臂的讀者們,就從這裡開始吧!

翦影

關於本智慧手臂教學照片請有興趣的讀者參考 12345678。桃園蘆竹五校聯合 AI 教育成果發表會之成果影片請參考圖 7 中之連結連入觀看。

 
圖 7:成果發表會影片

2022年5月8日 星期日

[Arduino, Maker] 「防疫大作戰」之機電整合分享:新北市三重區明志國中 2020 科技專案

  「防疫大作戰」之機電整合分享:

新北市三重區明志國中 2020 科技專案 [1]

Line:ted2016.kpvs
Email:Lct4246@gmail.com
FBhttp://gg.gg/TedLeeFB/
Bloghttp://gg.gg/TedLeeBlog/

May 8, 2022
88x31.png[2]


繼前文《「酷跑遊台灣」之機電整合分享:新北市108年資訊科技教育成果展專案》,本文仍以科技專案系列之機電整合技術分享為前題,揭露筆者於筆者 2020 年二月獲新北市三重區明志國中戴春城校長邀請,和進修部林耀坤主任創客達師(master maker)共同合作產出的作品(圖 1)。
本專案透過 Arduino 程式設計與感測器連結,發展出創新的機電整合課程。老師們使用 PBL 教學法指導學生自製闖關道具,運用課堂所學習到的數學及藝文關鍵素養,再結合健康促進之新冠病毒防疫議題,發展出動手實作的 STEAM 課程,以達到跨領域的學習目標,並具有寓教於樂的功能。

圖 2:筆者近四年來和明志國中的共同創作專案及作品

專案說明

明志國中為新北市 108 學年度國民中學科技領域輔導小組召集學校,亦為新北市程式教育示範學校,為作表率本校將在本展中展示結合科技、健體、社會、數學、藝文等跨領域課程之闖關活動「防疫大作戰」(圖 2)。
本專案由戴春成校長(圖 2 左1)領銜,協同進修部林耀坤主任(圖 2 右2)與筆者組成的專案小組指導若干學生們共同策畫與執行。

圖 2:防疫大作戰展示實景

操作方式為病毐聖戰士們手握如圖 3 中的重裝武器一同站立於黃色發射線後準備撃潰黑暗的病毐軍團。

圖 3:聖戰士們手持防禦性重兵器並齊立於黃色發起線後誓言要摧毀頑強的病毐大軍

每一回合按下圖 4 病毒轉盤左下的紅色按鈕(戴口罩小朋友頭頂左上方處)後藍色的病毒本體開始運轉。左上角的燈板會開始倒數計時兩分鐘。當聖戰士將子彈撃穿病毒的本體後,它就會中彈受傷。此時右上角的燈板就會累計聖戰上的戰績。

圖 4:囂張的病毒正在世界各地轉動肆虐著

專案架構

本專案依各自功能可區分為機構設計、機電整合電路設計與機電整合控制程式設計三大區塊。詳細說明如 1、2 及 3 小節所示。

1. 機構設計

本專案的機構設計乃由耀坤主任親自操刀,共分為射撃裝置和病毒轉盤兩件。 圖 5 中左二學生手持雷切製成的射撃槍即為射撃裝置一。它以內置的彈簧拉力來撃發乒乓球。此外,右一及右二學生手持的桶狀簡易射撃裝置是以回收的洋芋片罐加裝汽球以做為射撃的動力的來源。

圖 5:射撃裝置機構

圖 6 的病毒轉盤融合了耀坤主任精湛的木工技藝以及美術專長特製而成。轉盤上頭裝有計時和計分的燈板各一組。接著是以軸心連接後方齒輪的藍色病毒 COVID-19 模型。

圖 6:病毒轉盤

在病毒轉盤背後,上方是電路控制室。它剛好座落在圖 6 燈板的正後方。中間綠色小齒輪和 AC 馬達相接而形成齒輪動力組,藉此帶動和綠色大齒輪串在一起的圖 6 藍色病毒模型,內部中空相連情形如圖 8 所示。其中,圖 9 及圖 10 為耀坤主任親自指導學生以木工製作大小齒輪組的過程。
此外,圖 7 綠色大齒輪下方的集球溝內埋有一只細長柄的微動開關(micro switch)做計分偵測之用。

圖 7:齒輪動力組

圖 8:病毒轉盤的內部中空結構

圖 9:連接 AC 馬達的小齒輪

圖 10:帶動前方藍色病毒的大齒輪

2. 機電整合電路設計

本專案需求功能三:
  1. 計時:按下啟動按鈕後開始倒數 2 分鐘(120秒)並於燈板上顯示剩餘時間。
  2. 計分:有球進洞後需計分並於燈板上顯示累計得分。
  3. 重置(reset):按下啟動按鈕後轉動病毒轉盤重新開局。
由於我們尋獲的燈板是以 UART 和 Nano 開發板通信,而一塊 Nano 板只有一個 UART 通信通道。因而我們以 sss(signal to scoring Nano)接線來串接(cascade)計時 Nano(圖 11)和計分 Nano(圖 12)。 在圖 10的計時線路中,按鈕採下拉(pull down)設計以維持準確的電壓準位。和燈板以 D0(Rx)和 D1(Tx)兩支接腳進行序列傳輸。最後,使用一顆繼電器(relay)來控制病毒轉盤組的 AC 慢速馬達。 在圖 12 的計分電路中,燈板的接法和計時線路相同。三路的微動開關也是採用下拉的設計來處理。 整體的接線以模組化(modularized)的方式來佈局(layout),方便除錯和後續教學解說。完成後的電路控制箱如圖 13 和圖 14 所示。專案的所有材料清單如表 1 所示。

表 1:材料清單

品名

單位

數量

用途

1

NanoFT232晶片)

2

計分及計時燈板控制

2

mini USB傳輸線

2

計分及計時燈板控制

3

Lumex LDM-6432-P5-BLE4-1燈板

2

計分及計時燈板控制

4

麵包板

2

計分及計時燈板控制

5

延長線

1

電源供應控制

6

(藍)FB-002-34雙孔USB充電器

2

電源供應

7

杜邦(10cm

1

系統接線

8

杜邦(20cm

1

系統接線

9

單心線

1

系統接線

10

繼電器

1

AC馬達控制

11

按鈕開關

1

系統啟動按鈕

12

微動開関

1

計分感應

13

10KΩ電阻

2

啟動按鈕、計分感應電路


圖 11:計時接線圖

圖 12:計分接線圖

圖 13:電路控制箱

圖 14:電路控制箱近照

3. 機電整合控制程式設計

由於第 2 小節的模組化硬體設計,我們也以計時和計分來撰寫相對應的控制程式。這兩隻程式設計的流程圖如圖 15 所示。

圖 15:計時與計數程式的流程圖

計時程式在處理完按鈕彈跳(debounce)的機械特性後才進入計時燈板刷新(refresh)與繼電器控制馬達的程式邏輯。完整程式碼如下:

#define relay_pin 3

#define sss_pin 12

 

const int buttonPin = 2;    // the number of the pushbutton pin

 

// Variables will change:

//int ledState = HIGH;         // the current state of the output pin

int buttonState;             // the current reading from the input pin

int lastButtonState = LOW;   // the previous reading from the input pin

 

// the following variables are unsigned longs because the time, measured in

// milliseconds, will quickly become a bigger number than can be stored in an int.

unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled

unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

 

void ini() {

  lastButtonState = LOW;

  lastDebounceTime = 0;

}

 

void setup() {

  pinMode(relay_pin, OUTPUT);

  pinMode(buttonPin, INPUT);

  pinMode(sss_pin, OUTPUT);

 

  Serial.begin(115200);                 //設定UART

  delay(1000);

 

  Serial.print("atd0=()");              //清除畫面

  while (Serial.read() != 'E') {}       //等待燈板完成動作

 

  Serial.print("at2b=(0)");             //設定為覆蓋模式

  while (Serial.read() != 'E') {}       //傳送指令終止的字元

} //end of setup()

 

void loop() {

  // read the state of the switch into a local variable:

  int reading = digitalRead(buttonPin);

 

  // check to see if you just pressed the button

  // (i.e. the input went from LOW to HIGH), and you've waited long enough

  // since the last press to ignore any noise:

 

  // If the switch changed, due to noise or pressing:

  if (reading != lastButtonState) {

    // reset the debouncing timer

    lastDebounceTime = millis();

  }

 

  if ((millis() - lastDebounceTime) > debounceDelay) {

    // whatever the reading is at, it's been there for longer than the debounce delay, so take it as the actual current state:

 

    // if the button state has changed:

    if (reading != buttonState) {

      buttonState = reading;

 

      // only toggle the LED if the new button state is HIGH

      if (buttonState == HIGH) {

        //Spin the wheel

        digitalWrite(relay_pin, HIGH);

 

        //Score counting

        digitalWrite(sss_pin, HIGH);

 

        //Time counting

        //Serial.print("atef=(4)");               //設定文字顏色為綠色

        //while (Serial.read() != 'E') {}

 

        //Serial.print("at83=(1,0,Count:)");      //顯示8x16字型 Count: 字串

        //while (Serial.read() != 'E') {}

 

        Serial.print("atef=(32)");               //設定數字顏色為0Black3B4G32R

        while (Serial.read() != 'E') {}

 

        for (int i = 120; i > 0; i--) {

          Serial.print("atd0=()"); //清除畫面

          while (Serial.read() != 'E') {} //等待燈板完成動作

 

          Serial.print("at83=(1,3,");         //傳送部分指令

          Serial.print((String)i);                    //傳送指令中變動的部分

          Serial.write(')');                  //傳送指令終止的字元

          while (Serial.read() != 'E') {}

 

          delay(950);

 

          //Serial.print("atd0=()");              //清除畫面

          //while (Serial.read() != 'E') {}       //等待燈板完成動作

        } //end of for loop

 

        digitalWrite(sss_pin, LOW);

        digitalWrite(relay_pin, LOW);

      } //end of if

    } //end of if

  } //end of if

 

  // save the reading. Next time through the loop, it'll be the lastButtonState:

  lastButtonState = reading;

 

  //ini();

} //end of loop()


啟動按鈕被按下後,計時程式就會收到計時線路傳來的 sss 信號。當微動開關被聖戰士射入的乒乓球按壓後才進入計分燈板更新計分的程式邏輯。完整程式碼如下:

#define sss_pin 12

#define micro_sw_pin A0

 

int i = 0;

int x = 0;

 

float debounce_time = 1;

int start_time = 0;

int end_time = 0;

 

void setup() {

  pinMode(micro_sw_pin, INPUT);

  pinMode(sss_pin, INPUT);

 

 

  Serial.begin(115200);                 //設定UART

  delay(1000);

 

  Serial.print("atd0=()");              //清除畫面

  while (Serial.read() != 'E') {}       //等待燈板完成動作

 

  Serial.print("at2b=(0)");             //設定為覆蓋模式

  while (Serial.read() != 'E') {}       //傳送指令終止的字元

} //end of setup()

 

void loop() {

  while (digitalRead(sss_pin)) {

    Serial.print("atef=(4)");               //設定數字顏色為0Black3B4G32R

    while (Serial.read() != 'E') {}

 

    //Serial.print("atd0=()");

    //while (Serial.read() != 'E') {}

 

    x = analogRead(micro_sw_pin);

    if (x < 10) {

      //scoring();

      //i=10;

      ++i;

 

      Serial.print("atd0=()"); //清除畫面

      while (Serial.read() != 'E') {} //等待燈板完成動作

 

      Serial.print("at83=(1,3,");         //傳送部分指令

      Serial.print(i);                    //傳送指令中變動的部分

      Serial.write(')');                  //傳送指令終止的字元

      while (Serial.read() != 'E') {}

      delay(500);

    } //end of if

  } //end of while

 

  i=0;

} //end of loop()

 

void scoring() {

  //i=11;

  //int x = 0;

 

  //x = analogRead(micro_sw_pin);

  //Serial.println(x);

 

  start_time = millis();

 

  while (x <10) {

    //i=12;

    //Serial.print("start_time=");

    //Serial.println(start_time);

 

    if (x > 1000) {

      //i=13;

      end_time = millis();

      //Serial.print("end_time=");

      //Serial.println(end_time);

      //break;

    } //end of if

 

    x = analogRead(micro_sw_pin);   

  } //end of while

 

  //i = end_time - start_time;

  if ((end_time - start_time) > debounce_time ) {

    //i=14;

    //Serial.print("i=");

    //Serial.println(++i);

    ++i;

 

    start_time = 0;

    end_time = 0;

  }

}

未來擴充

本專案已展示了一個可供跨領課程的教具與多方融入多元教學雛型。當然,後續還能有不少可擴充及延伸的可能性,例如:燈板字體大小的調整、增加的燈光與音效、重置機制的安排、將控制電路洗成 PCB 板、…。筆者亦期待本專案能起一拋磚引玉之功效,提供有志於此類教學者一個起點的參考。