|

使用 Maker Uno 快速打造 Arduino入門成功經驗 [0]

   

作者:Ted Lee

什麼是 Maker Uno?

在開始討論 Maker Uno 之前,我們先回顧開發板(development board)界的經典 Arduino Uno。15 年前(2010),這塊只提供核心控制、所有的電子元件與感測器皆需以外接方式和微控制器(micro controller)相連接的板子,因為其開源硬體(open hardware)的特質,15 年來早就成為機電整合專案開發領域中為廣為人知的個中翹楚了!
然而,為了讓使用者更方便快速體驗電控程式的開發流程,馬來西亞的 Cytron 科技在 2017 年設計了板載復歸式(momentary)的按鈕開關(push button)(圖 1 中標示紅 1 處)、單色 LED(圖 1 中標示紅 2 處) 與蜂鳴器(圖 1 中標示紅 3 處)三種常見的電子元件的 Maker Uno。

圖 1:Maker Uno[2]

圖 1:Maker Uno[2]

為了免除硬體接線的困擾並讓初次開發 Arduino Uno 程式的初學者能快速上手,我們以 Maker Uno 再搭配生成式 AI(例如:ChatGPT)來生成板載硬體的程式碼。
本文期盼以最短、最無痛的學習曲線帶領讀者們輕輕敲開電控程式的入門磚!

軟、硬體開發環境測試

所有的軟、硬體專案在進行開發之前都需要先設定好開發環境development environment)。
首先,根據原廠手冊第 14 頁提供的 CH341 驅動程式(driver)連結,下載並安裝(圖 2)。

圖 2:CH341 驅動程式

接著,下載 Arduino IDE、安裝,再設定好開發板的型號(board)與序列埠(serial port)(圖 3),

圖 3:在 Arduino IDE 上設定好 Arduino Uno 板子與序列埠 COMx

最後,從「File -> Examples -> 01.Basics -> Blink」選擇 Arduino IDE 內建的 Blink 範例程式(圖 4)來測試板載接在 D13 腳位上的藍色 SMD LED 能否不斷閃鑠。

Blink 的完整程式碼如下,程式的設計邏輯是讓 D13 腳不斷重覆位輸出高電位來點亮 LED 一秒鐘,再輸出低電位關閉 LED 一秒鐘。


// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
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
}

板載電子元件一:按鈕開關(第 2 腳位)

板載的四隻腳的按鈕開關按下時電路導通形成電路迴路(loop);放開時則電路斷路(open circuit),圖 5。原廠手冊(Maker Uno User’s Manual.docx)上有提醒:使用前應先將第 2 腳位設定成拉升(pull-up)準位

圖 5:按鈕開關按下時由 1->0(左圖);按鈕開關按下時由 0->1(右圖)

接著,我們在 ChaGPT 中上傳「Maker Uno User’s Manual.docx」,再輸入以下咒語(prompt)(圖 6):

(人設)你是 Cytron Technology 的 Maker Uno 專家
(任務)
一個字一個讀「Maker Uno User’s Manual.docx」,並簡要說明你讀到的內容Push button 接在 pin 2,使用前要 configure as INPUT_PULLUP
按鈕按一下讓 D13 的 SMD LED 亮,再按一下滅
(輸出)生成 Uno 的程式碼

圖 6:ChatGPT 的回應

最後,生成的完整程式碼如下:


// Pin 定義
const int buttonPin = 2;      // 按鈕接在 D2
const int ledPin = 13;        // D13 為內建 SMD LED

// 狀態追蹤
bool ledState = false;
bool lastButtonState = HIGH;
bool buttonPressed = false;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);  // 啟用內部上拉電阻
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, ledState);    // 初始 LED 狀態
}

void loop() {
  bool currentButtonState = digitalRead(buttonPin);

  // 偵測按下的瞬間(由 HIGH 變 LOW)
  if (lastButtonState == HIGH && currentButtonState == LOW) {
    buttonPressed = true;
  }

  // 偵測放開的瞬間(由 LOW 變 HIGH)
  if (lastButtonState == LOW && currentButtonState == HIGH && buttonPressed) {
    ledState = !ledState;                 // 切換 LED 狀態
    digitalWrite(ledPin, ledState);       // 更新 LED 狀態
    buttonPressed = false;
  }

  lastButtonState = currentButtonState;   // 儲存目前狀態供下次比較
}

註:經筆者測試,本文的三個由 ChatGPT 生成的程式

板載電子元件二:單色 LED(第 D2~13 腳位)

LED(Light-Emitting Diode,發光二極體)的發光原理是將元件接上順向電壓後,這個半導體元件就會將供給的電能轉換成光能而發光。
首先,我們在 ChatGPT 上接續按鈕開關的例子再下達以下咒語:

(任務)
LED 接在 D2~D13
依序點亮各顆 LED
(輸出)生成 Uno 的程式碼

ChatGPT 生成的完整程式碼如下:


// Cytron Maker Uno - 依序點亮 D2~D13 上的 LED

void setup() {
  // 將 D2 ~ D13 設為輸出
  for (int pin = 2; pin <= 13; pin++) {
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW); // 初始為關閉
  }
}

void loop() {
  // 依序點亮每顆 LED
  for (int pin = 2; pin <= 13; pin++) {
    digitalWrite(pin, HIGH);  // 點亮
    delay(200);               // 停留 200 毫秒
    digitalWrite(pin, LOW);   // 關閉
  }

  delay(500); // 每輪間隔
}

板載電子元件三:蜂鳴器(第 8 腳位)

蜂鳴器是電磁效應:電生磁,磁生力。相關原理可參考這個影片。
我們在 ChatGPT 上接續按鈕開關的例子再下達以下咒語:
(任務)
蜂鳴器接在 D8
唱歌
(輸出)生成 Uno 的程式碼
ChatGPT 生成的完整程式碼如下:


// Cytron Maker Uno - 用蜂鳴器(D8)演奏簡單旋律

const int buzzerPin = 8;  // 蜂鳴器接腳

// 定義音符頻率 (C4~B4)
#define NOTE_C4  262
#define NOTE_D4  294
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_G4  392
#define NOTE_A4  440
#define NOTE_B4  494
#define NOTE_C5  523

// 簡單旋律:Do Re Mi Fa So La Ti Do(各 300 毫秒)
int melody[] = {
  NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4,
  NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5
};

int noteDuration = 300; // 每個音的持續時間(毫秒)

void setup() {
  // 無需初始化 tone,用時直接呼叫 tone()
}

void loop() {
  for (int i = 0; i < 8; i++) {
    tone(buzzerPin, melody[i], noteDuration);   // 播放音符
    delay(noteDuration * 1.3);                  // 加點間隔
  }

  delay(1000); // 播完一輪後暫停
}

結語

Arduino 生態系(eco system)經過了數十年的累積,正足以做為 ChatGPT 的先前訓練資料(pre-trained data),以致我們讓它生成 Maker Uno 的程式碼時是完全無縫接軌的。
這一步將撰寫程式碼的工作分派給 ChatGPT 的程式碼隱藏(code hiding)技巧,正足以讓初學者更專注在機電整合設計的上層邏輯思考。
因此,我們變成不怕做不出來,只怕想像不到。進而引領我們得以更深入地往「好設計」的指導原則大步邁進!

進階學習

以下數題提供給讀者們小試身手,仿照前文的咒語方式,自行設計合適的文字內容。再次提醒,生成式 AI(Generative AI,GenAI)的特質:

凡提問必有多套答案,但答案有可能是胡說八道!

      1. 按下按鈕時演奏著名的台語民謠:望春風。ChatGPT 產生的參考程式碼如下:
        
        
        // Maker Uno 彈奏《望春風》簡譜程式
        // 按鈕接在第 2 腳,蜂鳴器接在第 8 腳

        const int buttonPin = 2;    // 按鈕接在第 2 腳
        const int buzzerPin = 8;    // 蜂鳴器接在第 8 腳

        bool lastButtonState = HIGH;
        bool currentButtonState = HIGH;
        unsigned long lastDebounceTime = 0;
        unsigned long debounceDelay = 50;

        // 定義音符頻率 (Hz) - Key: C major, 4/4拍
        #define NOTE_C4  262
        #define NOTE_D4  294
        #define NOTE_E4  330
        #define NOTE_F4  349
        #define NOTE_G4  392
        #define NOTE_A4  440
        #define NOTE_B4  494
        #define NOTE_C5  523
        #define NOTE_D5  587
        #define NOTE_E5  659
        #define REST     0

        // 《望春風》簡譜 - 根據圖片轉換
        // 第一段:1 - 1 2 4 | 5 4 5 6 - | 1 · 6 6 5 4 | 5 - - - |
        // 第二段:6 · 1 1 6 | 4 · 5 5 - | 1 · 6 6 5 4 | 4 - - - |
        // 第三段:5 · 5 6 5 4 | 2 1 2 4 - | 2 · 4 5 4 6 | 1 - - - |
        // 第四段:1 · 1 2 1 6 | 6 5 4 2 - | 1 · 6 6 5 | 4 - - - ||

        int melody[] = {
          // 第一句:獨 夜無伴 守燈 下 清 風 對 面 吹
          NOTE_C4, NOTE_C4, NOTE_D4, NOTE_F4, NOTE_G4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_C4, NOTE_A4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_G4,
         
          // 第二句:十 七八歲 未 出嫁 想 著少年 家 開
          NOTE_A4, NOTE_C4, NOTE_C4, NOTE_A4, NOTE_F4, NOTE_G4, NOTE_G4, NOTE_C4, NOTE_A4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_F4,
         
          // 第三句:果 然標緻 面肉 白 誰 家 人 子 弟
          NOTE_G4, NOTE_G4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_D4, NOTE_C4, NOTE_D4, NOTE_F4, NOTE_D4, NOTE_F4, NOTE_G4, NOTE_F4, NOTE_A4, NOTE_C4,
         
          // 第四句:想 要問伊 驚歹 勢 心 內彈琵 慧
          NOTE_C4, NOTE_C4, NOTE_D4, NOTE_C4, NOTE_A4, NOTE_A4, NOTE_G4, NOTE_F4, NOTE_D4, NOTE_C4, NOTE_A4, NOTE_A4, NOTE_G4, NOTE_F4
        };

        // 音符時值 (毫秒) - 配合 4/4 拍
        int noteDurations[] = {
          // 第一句時值
          500, 250, 250, 500, 500, 250, 250, 750, 250, 250, 250, 250, 250, 1000,
         
          // 第二句時值  
          250, 250, 250, 500, 250, 250, 750, 250, 250, 250, 250, 250, 1000,
         
          // 第三句時值
          250, 250, 250, 250, 500, 250, 250, 250, 750, 250, 250, 250, 250, 500, 1000,
         
          // 第四句時值
          250, 250, 250, 250, 500, 250, 250, 250, 750, 250, 250, 250, 500, 1500
        };

        int melodyLength = sizeof(melody) / sizeof(melody[0]);

        void setup() {
          pinMode(buttonPin, INPUT_PULLUP);
          pinMode(buzzerPin, OUTPUT);
         
          Serial.begin(9600);
          Serial.println("《望春風》音樂播放器啟動");
          Serial.println("按下按鈕開始播放音樂");
        }

        void loop() {
          // 讀取按鈕狀態
          int reading = digitalRead(buttonPin);
         
          // 防彈跳處理
          if (reading != lastButtonState) {
            lastDebounceTime = millis();
          }
         
          if ((millis() - lastDebounceTime) > debounceDelay) {
            if (reading != currentButtonState) {
              currentButtonState = reading;
             
              // 按鈕被按下時播放音樂
              if (currentButtonState == LOW) {
                Serial.println("開始播放《望春風》");
                playMelody();
                Serial.println("播放完畢");
              }
            }
          }
         
          lastButtonState = reading;
        }

        void playMelody() {
          for (int i = 0; i < melodyLength; i++) {
            // 計算音符持續時間
            int noteDuration = noteDurations[i];
           
            if (melody[i] != REST) {
              // 播放音符
              tone(buzzerPin, melody[i], noteDuration);
              Serial.print("播放音符: ");
              Serial.print(melody[i]);
              Serial.print(" Hz, 持續: ");
              Serial.print(noteDuration);
              Serial.println(" ms");
            } else {
              // 休止符
              Serial.println("休止符");
            }
           
            // 等待音符播放完畢,加上短暫間隔
            delay(noteDuration);
           
            // 音符間的短暫停頓
            noTone(buzzerPin);
            delay(50);
          }
        }

        // 額外功能:播放單一音符測試
        void playTestNote(int frequency, int duration) {
          tone(buzzerPin, frequency, duration);
          delay(duration);
          noTone(buzzerPin);
        }

        // 額外功能:播放音階
        void playScale() {
          int scale[] = {NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4, NOTE_C5};
          int scaleLength = sizeof(scale) / sizeof(scale[0]);
         
          for (int i = 0; i < scaleLength; i++) {
            tone(buzzerPin, scale[i], 300);
            delay(350);
          }
          noTone(buzzerPin);
        }
      2. 接續第 1 題,設計一個讓 Maker Uno 可以動態點唱台語「四(季紅)月(夜愁)望(春風)雨(夜花)」的操作方式。
      3. 讓奇數編號的 LED 和偶數編號的 LED 交替閃爍。ChatGPT 產生的參考程式碼如下:
        
        
        // 程式範例 3:交替閃爍 (Alternating Flash)
        // 讓奇數編號的 LED 和偶數編號的 LED 交替閃爍

        const int ledPins[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 將 D3 到 D12 的 LED 腳位儲存在陣列中
        const int numLeds = sizeof(ledPins) / sizeof(ledPins[0]); // 計算 LED 數量
        const int flashDelay = 1000; // 亮滅之間的時間間隔 (毫秒)
        const int pauseDelay = 2000; // 兩組 LED 之間的時間間隔 (毫秒)

        void setup() {
          // 設定所有 LED 腳位為輸出模式
          for (int i = 0; i < numLeds; i++) {
            pinMode(ledPins[i], OUTPUT);
          }
        }

        void loop() {
          // 點亮奇數編號的 LED (例如 D3, D5, D7...)
          for (int i = 0; i < numLeds; i += 2) { // 每隔一個 LED 點亮
            digitalWrite(ledPins[i], HIGH);
          }
          delay(flashDelay);
          // 熄滅奇數編號的 LED
          for (int i = 0; i < numLeds; i += 2) {
            digitalWrite(ledPins[i], LOW);
          }
          delay(pauseDelay); // 暫停一下

          // 點亮偶數編號的 LED (例如 D4, D6, D8...)
          for (int i = 1; i < numLeds; i += 2) { // 從第二個 LED (索引 1) 開始點亮
            digitalWrite(ledPins[i], HIGH);
          }
          delay(flashDelay);
          // 熄滅偶數編號的 LED
          for (int i = 1; i < numLeds; i += 2) {
            digitalWrite(ledPins[i], LOW);
          }
          delay(pauseDelay); // 暫停一下
        }
      4. 讓 LED 從 D3 亮到 D12,再從 D12 亮回 D3。
        
        
        // 程式範例 2:來回閃爍 (Bounce Flash)
        // 讓 LED 從 D3 亮到 D12,再從 D12 亮回 D3

        const int ledPins[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 將 D3 到 D12 的 LED 腳位儲存在陣列中
        const int numLeds = sizeof(ledPins) / sizeof(ledPins[0]); // 計算 LED 數量
        const int delayTime = 80; // 每個 LED 亮滅之間的時間間隔 (毫秒)

        void setup() {
          // 設定所有 LED 腳位為輸出模式
          for (int i = 0; i < numLeds; i++) {
            pinMode(ledPins[i], OUTPUT);
          }
        }

        void loop() {
          // 從前往後依序點亮 LED
          for (int i = 0; i < numLeds; i++) { digitalWrite(ledPins[i], HIGH); // 點亮 LED delay(delayTime); // 延遲 digitalWrite(ledPins[i], LOW); // 熄滅 LED (只亮一下) } // 從後往前依序點亮 LED for (int i = numLeds - 2; i >= 0; i--) { // 注意這裡從倒數第二個開始,避免重複點亮最後一個
            digitalWrite(ledPins[i], HIGH); // 點亮 LED
            delay(delayTime); // 延遲
            digitalWrite(ledPins[i], LOW); // 熄滅 LED (只亮一下)
          }
        }
      5. 讓 LED 隨機亮起和熄滅。
        
        
        // 程式範例 4:隨機閃爍 (Random Flash)
        // 讓 LED 隨機亮起和熄滅

        const int ledPins[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 將 D3 到 D12 的 LED 腳位儲存在陣列中
        const int numLeds = sizeof(ledPins) / sizeof(ledPins[0]); // 計算 LED 數量

        void setup() {
          // 設定所有 LED 腳位為輸出模式
          for (int i = 0; i < numLeds; i++) {
            pinMode(ledPins[i], OUTPUT);
          }
          randomSeed(analogRead(A0)); // 使用 A0 腳位的類比讀值來初始化亂數種子,增加隨機性
        }

        void loop() {
          // 隨機選擇要點亮多少個 LED
          int numToLight = random(1, numLeds + 1); // 至少一個,最多所有 LED

          // 隨機選擇要點亮哪些 LED
          for (int i = 0; i < numToLight; i++) {
            int randomLedIndex = random(0, numLeds); // 隨機選擇一個 LED 索引
            digitalWrite(ledPins[randomLedIndex], HIGH); // 點亮該 LED
          }

          // 隨機亮燈時間
          delay(random(100, 500)); // 隨機延遲 100 到 500 毫秒

          // 熄滅所有 LED
          for (int i = 0; i < numLeds; i++) {
            digitalWrite(ledPins[i], LOW);
          }

          // 隨機熄燈時間 (下次亮燈前的間隔)
          delay(random(50, 300)); // 隨機延遲 50 到 300 毫秒
        }

         

      6. 讓 LED 從 D3 依序亮到 D12,然後再依序熄滅。
        
        
        // 程式範例 1:流水燈 (Running Light)
        // 讓 LED 從 D3 依序亮到 D12,然後再依序熄滅

        const int ledPins[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 將 D3 到 D12 的 LED 腳位儲存在陣列中
        const int numLeds = sizeof(ledPins) / sizeof(ledPins[0]); // 計算 LED 數量
        const int delayTime = 100; // 每個 LED 亮滅之間的時間間隔 (毫秒)

        void setup() {
          // 設定所有 LED 腳位為輸出模式
          for (int i = 0; i < numLeds; i++) {
            pinMode(ledPins[i], OUTPUT);
          }
        }

        void loop() {
          // LED 依序亮起
          for (int i = 0; i < numLeds; i++) {
            digitalWrite(ledPins[i], HIGH); // 點亮 LED
            delay(delayTime); // 延遲一段時間
          }

          // LED 依序熄滅
          for (int i = 0; i < numLeds; i++) {
            digitalWrite(ledPins[i], LOW); // 熄滅 LED
            delay(delayTime); // 延遲一段時間
          }
        }

     

[0]感謝竹南科技中心借用設備做為教學測試。
[1]六種授權條款
[2]圖片來源:https://docs.google.com/document/d/1gJIF-tssdoxk8qAq2aHBmPwVHulFL_lSElOhA17Du64/mobilebasic。

(作者為本刊專欄作家,本文同步表於作者部落格,原文連結;責任編輯:謝涵如)

Ted Lee

訂閱MakerPRO知識充電報

與40000位開發者一同掌握科技創新的技術資訊!

Author: Ted Lee

從工程師轉任中學教師,又為了捍衛教育理念,投身成為 STEAM 教育工作者,自稱「無可救藥的人文教育理想主義者」的李俊德(Ted Lee)。

Share This Post On

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *