# 生活科技筆記
## 1. OnShape 3D 建模
OnShape 是一個免費線上 3D 建模網站。

梅開板手(一邊是**十二邊形**,一邊是**六邊形**)
---
### 作業:機器人小車建模
其實不難,但我遇到兩個問題。
* 因為我視力比較不好需要放大,而放大之後有些功能就會一時找不到,要用搜尋的方法找到它。
==解決方法:搜尋也不是一件麻煩的事,沒關係,以後不要找不到就以為沒有就好。==
* **不知道怎麼改圓的半徑**。在老師的影片裡,看起來像點完圓心及圓上一點後,點一下旁邊的半徑數字再輸入半徑。但是我發現如果這麼做,會變成再畫一個圓。
==解決方法:後面的影片有說滑鼠游標指到數字上後直接輸入就好。==
成果圖:

---
**心得:原來 3D 建模比我想像中容易。**
## 2. TinkerCAD 基礎電路設計
TinkerCAD 也是 3D 繪圖網站,它還有電路模擬器。
成果圖片:

發亮的 LED

滑動開關 1

滑動開關 2
---
### 作業:滑動開關,左邊串聯兩個紅色 LED ,右邊並聯兩個綠色 LED
我一開始的電路是這樣,經過計算:
電池為 9V ,一個 LED 要 2V 和 0.02A 。
* 左邊串聯部分,電阻設定為 5V / 0.02A = 250 歐姆
* 右邊並聯部分,電阻設定為 7V / 0.04A = 175 歐姆。

結果根本無法開始模擬,**初始化之後就直接結束了。**
我一直 Debug , *把連接其中一個綠色 LED 的線路拿掉* 或是 *把連接紅色 LED 的線路拿掉* 都成功,但是**全部一起**就是不行。
我嘗試到網路上找資料發現找不到。
最後問 ChatGPT ,他一開始以為我是在用並聯電池,接下來以為中間那個是 IC 。
他建議把紅色 LED 的電阻提高,於是我隨便選了個大於 250 的數字,是 300 ,接著按下 “ ***開始模擬*** ”,成功了。但是完全不知道原因。
疑似是 TinkerCAD 的 Bug ,老師也這麼認為。
---
### 第一次段考前的生活科技課,我忘記是自修,沒帶書,做的實驗
當 235 <= 紅色 LED 電阻 <= 255 時,模擬無法開始。
改成**兩個電池並聯**或是**以 Arduino 供電**,兩個電阻重算後,也成功。
---
**心得:原來模擬器和實際電路無法完全相同,有時候模擬器會出現實際上根本不會遇到的 bug 。這時候不需要太執著理論數值,就像科學實驗會有誤差一樣。**
## 3. Arduino
在 TinkerCAD 的模擬器中也可以寫 Arduino 程式。
成果:

閃爍的 LED
---
### 平交道號誌

[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);
}
```
---
### 跑馬燈

[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);
}
}
```
---
### 呼吸燈

[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 沒有所以在模擬器中以按鈕代替。

[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 亮度

我一開始的程式是這樣的
```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 的實用性。**
---
### 光敏電阻

[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 兩個腳位。

圖片來源:維基百科。由 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° ,用於精準控制角度。

```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 度的範圍內反覆轉向。
### 實作測驗

任務:循跡到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 粉塵感測器。