---
# System prepended metadata

title: 生活科技筆記
tags: [3D建模, 學習歷程, 程式設計, Arduino]

---

# 生活科技筆記

## 1. OnShape 3D 建模

OnShape 是一個免費線上 3D 建模網站。

![Screenshot 2025-03-19 082401](https://hackmd.io/_uploads/BJVm19w31x.png)
梅開板手(一邊是**十二邊形**，一邊是**六邊形**)

---

### 作業：機器人小車建模

其實不難，但我遇到兩個問題。

* 因為我視力比較不好需要放大，而放大之後有些功能就會一時找不到，要用搜尋的方法找到它。
==解決方法：搜尋也不是一件麻煩的事，沒關係，以後不要找不到就以為沒有就好。==
* **不知道怎麼改圓的半徑**。在老師的影片裡，看起來像點完圓心及圓上一點後，點一下旁邊的半徑數字再輸入半徑。但是我發現如果這麼做，會變成再畫一個圓。
==解決方法：後面的影片有說滑鼠游標指到數字上後直接輸入就好。==

成果圖：
![Screenshot 2025-03-19 083859](https://hackmd.io/_uploads/HJc4zcDhyg.png)

---

**心得：原來 3D 建模比我想像中容易。**

## 2. TinkerCAD 基礎電路設計

TinkerCAD 也是 3D 繪圖網站，它還有電路模擬器。

成果圖片：

![Screenshot 2025-03-05 093507](https://hackmd.io/_uploads/HJ3hc7rj1g.png)
發亮的 LED

![螢幕擷取畫面 2025-03-09 165129](https://hackmd.io/_uploads/B1Bp-kjjyg.png)
滑動開關 1

![螢幕擷取畫面 2025-03-09 165138](https://hackmd.io/_uploads/rJP1GJsiJg.png)
滑動開關 2

---

### 作業：滑動開關，左邊串聯兩個紅色 LED ，右邊並聯兩個綠色 LED

我一開始的電路是這樣，經過計算：

電池為 9V ，一個 LED 要 2V 和 0.02A 。

* 左邊串聯部分，電阻設定為 5V / 0.02A = 250 歐姆
* 右邊並聯部分，電阻設定為 7V / 0.04A = 175 歐姆。

![螢幕擷取畫面 2025-03-09 171950](https://hackmd.io/_uploads/rJkE71joJg.png)

結果根本無法開始模擬，**初始化之後就直接結束了。**

我一直 Debug ， *把連接其中一個綠色 LED 的線路拿掉* 或是 *把連接紅色 LED 的線路拿掉* 都成功，但是**全部一起**就是不行。

我嘗試到網路上找資料發現找不到。

最後問 ChatGPT ，他一開始以為我是在用並聯電池，接下來以為中間那個是 IC 。

他建議把紅色 LED 的電阻提高，於是我隨便選了個大於 250 的數字，是 300 ，接著按下 “ ***開始模擬*** ”，成功了。但是完全不知道原因。

疑似是 TinkerCAD 的 Bug ，老師也這麼認為。

---

### 第一次段考前的生活科技課，我忘記是自修，沒帶書，做的實驗

當 235 <= 紅色 LED 電阻 <= 255 時，模擬無法開始。

改成**兩個電池並聯**或是**以 Arduino 供電**，兩個電阻重算後，也成功。

---

**心得：原來模擬器和實際電路無法完全相同，有時候模擬器會出現實際上根本不會遇到的 bug 。這時候不需要太執著理論數值，就像科學實驗會有誤差一樣。**

## 3. Arduino

在 TinkerCAD 的模擬器中也可以寫 Arduino 程式。

成果：

![Screenshot 2025-03-12 092549](https://hackmd.io/_uploads/H1dafv0s1g.png)
閃爍的 LED

---

### 平交道號誌

![螢幕擷取畫面 2025-04-29 203505](https://hackmd.io/_uploads/ryuhPS0Jlx.png)

[Arduino 平交道號誌影片連結](https://youtu.be/flvlnSG0mFk)

程式碼:

``` cpp=
void setup() {
    // LEDs are connected to pin12 and pin13.
    pinMode(13, OUTPUT);
    pinMode(12, OUTPUT);
}

const int t = 500; // delay time (ms)

void loop() {
    digitalWrite(13, HIGH);
    digitalWrite(12, LOW);
    delay(t);
    digitalWrite(13, LOW);
    digitalWrite(12, HIGH);
    delay(t);
}
```

---

### 跑馬燈

![螢幕擷取畫面 2025-04-29 203709](https://hackmd.io/_uploads/HyNMuBAklx.png)

[Arduino 跑馬燈影片連結](https://youtu.be/mlkKAL95aEs)

程式碼：

```cpp=
void setup() {
    for (int i = 9 ; i < 14 ; i ++) pinMode(i, OUTPUT);
}

const int t = 200;
void loop() {
    for (int i = 9 ; i < 14 ; i ++) {
      digitalWrite(i, HIGH);
      for (int j = 9 ; j < 14 ; j ++) {
        if (j != i ) digitalWrite(j, LOW);
      }
      delay(t);
    }
    for (int i = 12 ; i > 9 ; i --) {
      digitalWrite(i, HIGH);
      for (int j = 9 ; j < 14 ; j ++) {
        if (j != i ) digitalWrite(j, LOW);
      }
      delay(t);
    }
}
```

---

### 呼吸燈

![螢幕擷取畫面 2025-04-29 203834](https://hackmd.io/_uploads/ByuL_H0yle.png)

[Arduino 呼吸燈影片連結](https://youtu.be/9VS17z4Mfwk)

程式碼：

```cpp=
void setup() {
    pinMode(9, OUTPUT);
}

const int t = 10;
void loop() {
    for (int i = 0 ; i < 255 ; i ++) {
      analogWrite(9, i);
      delay(t);
    }
    for (int i = 255 ; i >= 0 ; i --) {
      analogWrite(9, i);
      delay(t);
    }
}
```

---

### 觸摸感測器

這是電容式觸碰感測器。TinkerCAD 沒有所以在模擬器中以按鈕代替。

![螢幕擷取畫面 2025-07-01 161335](https://hackmd.io/_uploads/BJwpuz-Ble.png)

[Arduino 觸摸感測器影片連結](https://youtu.be/RprlG2-t8RY)

程式碼：

```cpp=
void setup() {
  pinMode(3, OUTPUT);
  pinMode(13, INPUT);
}

void loop() {
  bool touched = digitalRead(13);
  if (touched) digitalWrite(3, HIGH);
  else digitalWrite(3, LOW);
}
```

---

### 可變電阻調整 LED 亮度

![螢幕擷取畫面 2025-04-05 162824](https://hackmd.io/_uploads/ryLAKv06Jg.png)

我一開始的程式是這樣的

```cpp=
void setup() {
  pinMode(A0, INPUT);
  pinMode(10, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int pot = analogRead(A0);
  analogWrite(10, pot*255/1023);
  Serial.print(pot);
  Serial.print(" ");
  Serial.print(pot*255/1023);
  Serial.print("\n");
}
```

讀入從 0 到 1023 ，為避免整數除法使明暗變化不夠平順，先乘 255 再除 1023 ，得到從 0 到 255 的整數。

執行發現 **LED 忽亮忽暗，不是預期中越往右轉越亮**。

我覺得很奇怪，懷疑又是 TinkerCAD 的 Bug。

打開 Serial Monitor 之後，發現傳入 `analogWrite()` 的數值出現 ***負數*** 。

我一開始就算過 1023 * 255 不會超過 2^31^ ，覺得不會溢位卻出現負數。

問了 ChatGPT 才發現原來 ==Arduino 的 `int` 跟平常電腦上 C / C++ 的 `int` 範圍不一樣==。

在發現 Arduino UNO 系列有最新版 R4 後，發現事情不是一般電腦和 MCU 的區別這麼單純。

* 電腦 C++ 及新版 Arduino Uno R4 的 `int` 為 32 bit ，最大可達 2^31^ - 1 。
* Arduino Uno R3 的 `int` 是 16 bit ，最大只能到 2^15^ - 1 = 32767 。

在實體或 TinkerCAD 模擬的 Arduino Uno R3 執行程式時，若 pot * 255 > 32767 ，就會溢位變成負數導致執行結果無法預期。

實際上，把 0 ~ 1023 轉換成 0 ~ 255 有幾種方法。

1. 直接放進 `analogWrite()` 它會自動對 256 取餘。但不是我預期的功能。
2. 使用 `constrain(pot, 0, 255)` ，小於 0 會轉換成 0，大於 255 會轉換成 255。也不是我預期的樣子。
3. 發現 1023 約為 255 的 4 倍，於是使用 `pot / 4` 轉換。
4. 通用的方法，使用 `map(pot, 0, 1023, 0, 255)` ，可將任意範圍透過線性映射轉換成另一個範圍。

還有一個小問題，連寫數個 `Serial.print()` 在 `loop()` 裡面看起來就有些冗長，於是我把這些 `print` 包成一個函式。

最終程式：

```cpp=
void setup()
{
  pinMode(A0, INPUT);
  pinMode(10, OUTPUT);
  Serial.begin(9600);
}

void log(int a, int b) {
  Serial.print(a);
  Serial.print(" ");
  Serial.println(b);
}

void loop() {
  int pot = analogRead(A0);
  analogWrite(10, pot/4);
  log(pot, pot/4);
}
```

[成果影片連結](https://youtu.be/1W2N-pGaMEY)

**心得：原來在不同的平台，同樣的型別名也會有不一樣的範圍，Arduino Uno R3 的處理器是 8 bit ， R4 的處理器則是 32 bit 。**
**這次經驗也讓我更了解用輸出 Debug 的實用性。**

---

### 光敏電阻

![螢幕擷取畫面 2025-07-01 160616](https://hackmd.io/_uploads/HknbDGWrel.png)

[Arduino 光敏電阻影片連結](https://youtu.be/7qWIPNYvUXc)

程式碼

```cpp=
void setup() {
  pinMode(A0, INPUT);
  pinMode(3, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  digitalWrite(3, analogRead(A0) > 800);
}
```

### 自主學習區

#### a. 關於腳位 D0 和 D1

D0 和 D1 是 Arduino 的序列埠通訊腳位，MCU 透過這兩個腳位與 USB 轉序列埠晶片連接。

故若在這兩個腳位接上其他裝置，可能干擾程式上傳等任何與 USB 通訊相關的程序。

>[!Note] 以下內容為上傳學習歷程後補
Uno R4 的 USB 介面為原生支援，D0 和 D1 可用作一般腳位和 UART 介面。

---

#### b. 隨機數

```cpp=
void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(0));
}

void loop() {
  n = random(1, 11);
  Serial.println(n);
  delay(200);
}
```

`analogRead()` 裡可以直接寫 0，也代表 A0 。

A0 為未接腳時數值會因雜訊而亂跳，適合作為亂數種子。

1 和 6 分別為最小值和最大值，包含最小值但不包含最大值。

如果沒有寫最小值，亂數範圍就是 `0 ~ 最大值 - 1` 。

---

#### c. `pulseIn()`

有些感測器不是回傳固定電壓，是回傳一個脈衝訊號，此時就要用這個函式。語法如下。

```cpp=
unsigned long in = pulseIn(pin, HIGH, timeout);
```

`pin` 為要讀取的腳位，可以是任何腳位。

第二個參數可以是 `HIGH` 或 `LOW` ，為要讀取的脈衝。

第三個參數可有可無，是最長等待時間，單位為微秒。

---

#### d. `millis()` 和 `delay()` 的差異

`delay()` 會讓程式整個卡在進入 `delay()` 前一刻的狀態，不會讀取感測器的數值也不會改變任何腳位的輸出。

這會導致遺漏感測器訊息，只能一次做一件事。

`millis()` 則是回傳從啟動到現在經過的毫秒數，型別為 `unsigned long` 。

使用 `if (millis() - prevMillis >= t)` 可以達到**等待 t 毫秒** 的效果，不會讓程式卡住，可以邊做其他事情。

---

#### e. Uno R4

Arduino Uno R4 於 2023 年上市，有 DAC 等 R3 沒有的功能，WiFi 版有 WiFi 和藍芽。

PWM 是用占空比模擬連續電壓，DAC 是真的輸出連續電壓，Uno R4 的 DAC 腳位為 A0 。

Uno R4 WiFi 有 WiFi 模組，可連線到 Arduino IoT Cloud 或是任何線上 API 。也可以作為 Webserver 讓其他裝置連線遠端控制。

Uno R4 WiFi 還有一個 12 * 8 的 LED 矩陣，可以顯示文字和圖案。

## 4. 輪型機器人（RP2040）

不是用 Arduino 有以下原因：

1. Arduino 反應沒有 RP2040 快。Arduino Uno R3 的 CPU 是 16 MHz，R4 是 48 MHz。RP2040 是 133 MHz。
2. Arduino 控制馬達要接擴充板比較麻煩。

老師已經寫好了大部分的程式。

重啟指令如下。

```cpp=
void resetFunc() {  // restart
  watchdog_reboot(0, 0, 0); 
  // 第一個 0 是重啟後程式開始的位置。
  // 第二個 0 是重啟後堆疊的初始位置。
  // 第三個 0 是從執行到重啟的毫秒數。
}
```

### a. 直流馬達

每個馬達都各自透過 H 橋電路接到 A 、 B 兩個腳位。

![H_bridge](https://hackmd.io/_uploads/HkVOiGbHxg.svg)

圖片來源：維基百科。由 Cyril BUTTAY - 自己的作品, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=854051

在馬達運轉時，將S1和S3閉合或將S2和S4閉合，即短接馬達兩端，則馬達因反電流被快速剎停，若將H橋的全部開關斷開，則馬達自由停轉

```cpp=
void drive(int powerL, int powerR) {  // left and right motor control
  // powerL: Left motor direction and power.
  // powerR: Right motor direction and power.

  powerL = constrain(powerL, -255, 255);  // -255<= powerL <= 255
  powerR = constrain(powerR, -255, 255);  // -255<= powerR <= 255

  if (powerL > 0) {
    analogWrite(pinM1A, powerL);
    analogWrite(pinM1B, 0);
  } else {
    analogWrite(pinM1A, 0);
    analogWrite(pinM1B, -powerL);
  }
  if (powerR > 0) {
    analogWrite(pinM2A, powerR);
    analogWrite(pinM2B, 0);
  } else {
    analogWrite(pinM2A, 0);
    analogWrite(pinM2B, -powerR);
  }
}

void coast() {  // stop the car and coast
  analogWrite(pinM1A, 255);
  analogWrite(pinM1B, 255);
  analogWrite(pinM2A, 255);
  analogWrite(pinM2B, 255);
  delay(1000);
}

void brake() {  // stop the car and brake
  analogWrite(pinM1A, 0);
  analogWrite(pinM1B, 0);
  analogWrite(pinM2A, 0);
  analogWrite(pinM2B, 0);
  delay(500);
}
```

### b. 超音波感測器

原理：超音波反射。

太近、太遠、角度太斜、物體吸音都會導致量不到。

```cpp=
float checkDistance() {  // detection distance
  // Use ultrasonic to detect the distance of obstacles in centimeters.
  digitalWrite(pinTrig, LOW);
  delayMicroseconds(2); // 2 微秒，等於 0.002毫秒
  digitalWrite(pinTrig, HIGH); // 發射超音波
  delayMicroseconds(10);
  digitalWrite(pinTrig, LOW);

  // Measure the response from the HC-SR04P Echo Pin
  int duration = pulseIn(pinEcho, HIGH);

  // Determine distance from duration
  // Use 343 meters per second as speed of sound
  float distance = duration * 0.034 / 2;
  delay(20);  // Pause to prevent frequent reading 避免被之前的超音波干擾
  return distance;
}
```

### c. 紅外線感測器

原理：反射紅外線

```cpp=
int checkColor() {  // detection black and white
  // Use digital signals from infrared reflective sensor to detect black and white
  // Returns 0 for black and 1 for white.
  int IR_D = digitalRead(pinIR_D);  // 0:white 1:black 因為反射光越多電阻越小，數值越小
  delay(1);  // Pause to prevent noise caused by frequent reads.
  return !IR_D;  // Invert, 0:black  1:white
}
```

#### 循線

並非真正的循跡，僅是根據看到的顏色判斷左轉或右轉。

若直接寫一邊是 180 一邊是 0，會不斷搖晃。

經過測試發現一邊是 150 另一邊 110 很穩。

```cpp=
void track() {
  if (checkColor()) drive(110, 150);
  else drive(150, 110);
}
```

#### 找第 n 條黑線

我最開始的程式是這樣：

```cpp=
void findLine(int n) {
  int curr = 0;
  while (curr < n) {
    drive(150, 150);
    if (checkColor() == 0) curr ++;
  }
  brake();
}
```

發現機器人在第一條黑線就停下，原因是程式執行非常快，看到第一條黑線的不到一毫秒內 curr 就可以累加到 n 導致停下。

解決方法是在偵測到黑線後繼續前進直到偵測到白色，再將 curr 加一。

```cpp=
void findLine(int n) {
  int curr = 0;
  while (curr < n) {
    drive(150, 150);
    if (checkColor() == 0) {
      while (!checkColor());
      curr ++;
    }
  }
  brake();
}
```

### d. 伺服馬達

只能轉 0° ~ 180° ，用於精準控制角度。

![爪子](https://hackmd.io/_uploads/SJD-pf-Sgl.jpg)

```cpp=
void inPosition() {  // Servo motor initial positioning
  handL.write(180);
  handR.write(0);
  delay(1000);
}

void closeHands() {
  for (int i = 0; i <= 170; i++) {
    handL.write(180 - i);
    handR.write(i);
    delay(7);
  }
}

void openHands() {
  for (int i = 0; i <= 170; i++) {
    handL.write(i + 10);
    handR.write(170 - i);
    delay(7);
  }
}

void leftHook() {
  handL.write(0);
  delay(1000);
  handL.write(180);
  delay(1000);
}

void rightHook() {
  handR.write(180);
  delay(1000);
  handR.write(0);
  delay(1000);
}
```

如果 `.write(angle)` 後面沒有 `delay()` 就直接 write 下一個角度，會看到馬達不斷在約 30 度的範圍內反覆轉向。

### 實作測驗

![螢幕擷取畫面 2025-07-01 163545](https://hackmd.io/_uploads/H18lAM-Hxx.png)

任務：循跡到A，閃開A後繼續循跡到B，並使用爪子打倒B，循跡到C，並使用爪子抱住C，循跡回終點並停車並放開C。

試了超多次不是 A 那裡不穩定就是看不到 B。

發現 A 那邊不穩定是因為大量使用 `delay` 控制走的距離，後來用找黑線的方法就穩定了。

看不到 B 是因為角度太斜，需要把 B 放外面一點。

[成果影片](https://youtu.be/2aku2yZSsgM)

## 5. 心得總結

我非常喜歡這學期的生活科技課，尤其是 Arduino 的部分。

其實我很早就知道 Arduino 了只是沒有寫程式，從上個學期我就非常期待學到 Arduino 。

下學期的自主學習計畫就是 Arduino ，使用 Uno R4 WiFi 製作環境監測系統，整合溫溼度感測器和 PM2.5 粉塵感測器。
