這份課堂紀錄從 Arduino 與 Tinkercad 的練習開始,涵蓋串列輸出、感測器、LED、七段顯示器與中斷。內容依上課日期排列,搭配電路畫面和程式碼,方便回顧每次實作。

本篇重點
  • 依日期查找電路配置與程式範例
  • 觀察感測器輸入與 LED、顯示器的輸出
  • 比較傳值方式、函式與中斷控制的實作

原始實作記錄於 2019 年;以下操作畫面與程式範例保留當時版本。

以下依上課日期保留電路、程式與當時結果。先核對電路接線與腳位,再閱讀程式中的輸入、判斷與輸出,較容易理解各次練習的變化。

請至 Tinkercad 註冊帳號。
若已有 Autodesk 帳號,可依 Tinkercad 的登入方式使用既有帳號。
登入後,開啟原紀錄中的左上方 LOGO,找到Circuits(電路),再建立電路專案並加入元件。這是 2019 年介面紀錄,實際入口以使用版本為準。


2019年10月24日

先練習把資料送到電腦,再用週期性資料觀察波形與公式輸出。確認串列傳輸速率與監視器設定一致,才能正確閱讀數值。

傳資料到 PC

1
2
3
4
5
6
7
8
9
10
11
const byte ledPin = 13;

void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
Serial.println("Hello," ); // ln換行
Serial.print("\tLED pin is: ");
Serial.print(ledPin);
Serial.print("\nBYE!");
}
void loop(){
}
  • 執行後,結果為
    1
    2
    3
    Hello,
    LED pin is: 13
    BYE!

修改後的練習

1
2
3
4
5
6
7
8
9
10
11
const long ledPin = 525566;

void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
Serial.println("Hello," ); // ln換行
Serial.print("\tYongCheng: ");
Serial.print(ledPin);
Serial.print("\nBYE!");
}
void loop(){
}
  • 執行後,結果為
    1
    2
    3
    Hello,
    YongCheng: 525566
    BYE!

顯示正弦波

1
2
3
4
5
6
7
8
9
10
11
12
13
void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
}

void loop(){
float angle = 0;float sineData;
for (angle = 0.0; angle <= 90; angle += 0.1)
{
sineData = sin(angle);
Serial.println(sineData);
delay(1);
}
}
  • 執行後,顯示為
    顯示正弦波:原文圖片 01

串列監視器畫出心形

先參考前面的「顯示正弦波」,了解資料輸出,再嘗試心形公式。我自己Google到的心形公式

(sqrt(cos(x))*cos(200*x)+sqrt(abs(x))-0.7)*(4-x^2)^0.01)
Arduino 的 C++ 語法中,^ 是位元 XOR,不是次方;次方可用 pow,平方也可寫成 x*x。下方保留原公式,但其中 4-x^2 仍需修正為相符的數學運算;也要注意平方根與分數次方的定義域。(pow的意思可以參考這裡連結)
(sqrt(cos(x))*cos(200*x)+sqrt(abs(x))-0.7)*pow((4-x^2),0.01)

1
2
3
4
5
6
7
8
9
10
11
void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
double angle = 0;float sineData;
for (angle = -4.5; angle <= 4.5; angle += 0.008){
double x=angle;
Serial.println(sqrt(cos(x))*cos(200*x)+sqrt(abs(x))-0.7)*pow((4-x*x),0.01);
}
}

void loop(){
}
  • 執行後,顯示為
    串列監視器畫出心形:原文圖片 02

老師找到的心形公式

(sqrt(cos(x))*cos(300*x)+sqrt(abs(x))-0.2)*pow((4-x*x),0.01)

1
2
3
4
5
6
7
8
9
10
11
void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
double angle = 0;float sineData;
for (angle = -4.5; angle <= 4.5; angle += 0.008){
double x=angle;
Serial.println(sqrt(cos(x))*cos(300*x)+sqrt(abs(x))-0.2)*pow((4-x*x),0.01); //
}
}

void loop(){
}
  • 執行後,顯示為
    串列監視器畫出心形:原文圖片 03

試做一個電壓調節器

建立新的電路專案,加入一個Arduino Uno R3和電位器。先核對電源、接地及訊號腳位,再測試輸入數值如何變化。程式碼新增:

1
2
3
4
5
6
7
8
9
10
11
12
// 詳細的程式說明,請參閱第六章,6-4頁。
const byte potPin = A0;
int val; // 接收類比輸入值的變數,類型為整數。

void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
}
void loop(){
val = analogRead(potPin);
Serial.println(val);
delay(500);
}
  • 執行後,顯示為
    試做一個電壓調節器:原文圖片 04

修改後的練習

以類比讀值 512 為門檻控制 LED:先看程式使用 > 還是 >=,再判斷恰好等於 512 時屬於哪個分支。
讀值在另一個分支時關閉 LED。門檻與比較方向應依實際電路及程式確認。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const byte potPin = A0;
int val; // 接收類比輸入值的變數,類型為整數。

void setup(){
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
pinMode(13,OUTPUT);
}
void loop(){
val = analogRead(potPin);
Serial.println(val);
if(val>512){
digitalWrite(13,HIGH);
}
else
{
digitalWrite(13,LOW);
}
}
  • 執行後,顯示為
    更改內容test:原文圖片 05

2019年10月28日

這週把感測器輸入接到 LED 與七段顯示器。先只改變一個輸入,觀察數值,再確認程式判斷與輸出是否一致。

使用光敏電阻控制LED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const byte LED = 13;
const byte CdS = A0;

void setup()
{
Serial.begin(9600); // 以 9600bps 速率初始化序列埠
pinMode(13, OUTPUT);
}

void loop(){
int val; // 接收類比輸入值的變數,類型為整數
val = analogRead(CdS);
Serial.println(val); // 印val數值
if(val>=700){
digitalWrite(13,HIGH);
}
else{
digitalWrite(13,LOW);
}
}
  • 執行後,顯示為
    使用光敏電阻控制LED:原文圖片 06

74HC595控制「七段顯示器」

利用 74HC595 控制七段顯示器,依序顯示 0~9;先對照接線與各段編碼,再觀察輸出順序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const byte datePin = 2;  // 74HC595 序列輸入腳接「數位2」
const byte latchPin = 3; // 74HC595 暫存器時脈腳接「數位3」
const byte clockPin = 4; // 74HC595 序列時脈腳接「數位4」

byte index = 0;

const byte LEDs[10] = { // 七段顯示器索引
B01111110, // 0
B00110000, // 1
B01101101, // 2
B01111001, // 3
B00110011, // 4
B01011011, // 5
B01011111, // 6
B01110000, // 7
B01111111, // 8
B01110011}; // 9

void setup()
{
pinMode(datePin, OUTPUT);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
}

void loop()
{
digitalWrite(latchPin, LOW); // 關閉閘門
// 底下的函數將從 LEDs 陣列取出一個位元組資料(從 0 的元素開始取)
// 並以序列方式傳入 74595 IC
shiftOut(datePin,clockPin, LSBFIRST, LEDs[index]);
digitalWrite(latchPin, HIGH); // 開啟閘門
delay(1000); // 暫停一秒

index ++;
if (index == 10){ // 10個數字
index = 0;
}
}
  • 執行後,顯示為
    74HC595控制「七段顯示器」:原文圖片 07

修改後的練習

將七段顯示器顯示為「9、7、5、3、1」
電路配置與上一個範例相同,這次主要修改顯示順序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const byte datePin = 2;
const byte latchPin = 3;
const byte clockPin = 4;

byte index = 0;

const byte LEDs[5] = {
B01110011, // 9
B01110000, // 7
B01011011, // 5
B01111001, // 3
B00110000}; // 1

void setup()
{
pinMode(datePin, OUTPUT);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
}

void loop()
{
digitalWrite(latchPin, LOW);
shiftOut(datePin,clockPin, LSBFIRST, LEDs[index]);
digitalWrite(latchPin, HIGH);
delay(1000); // Wait for 1000 millisecond(s)

index ++;
if (index == 5){ // 5個數字
index = 0;
}
}
  • 顯示結果為,依序「9、7、5、3、1」循環。
    更改TEST:原文圖片 08

2019年11月11日

本段保留超音波與輸出控制練習。測距需對照模組接線與觸發方式;控制馬達時,也需確認驅動電路,而不是僅靠程式輸出腳位。

超音波

課本範例(9-29)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const byte trigPin = 10;  // 超音波模組的觸發腳
const int echoPin = 9; // 超音波模組的接收腳
unsigned long d; // 儲存高脈衝的持續時間

// 自訂 ping()函數將傳回 unsigned long 類型的數值
unsigned long ping(){
digitalWrite(trigPin, HIGH); // 觸發腳設定成高電位
microseconds(10); // 持續 10 毫秒
digitalWrite(trigPin, LOW); // 觸發腳設定成低電位
return pulseIn(echoPin, HIGH); //傳回高脈衝的持續時間
}
void setup()
{
pinMode(trigPin, OUTPUT); // 觸發腳設定成[輸出]
pinMode(echoPin, INPUT); // 接收腳設定成[輸入]

Serial.begin(9600); //初始化序列埠
}

void loop()
{
d = ping() /58; // 把高脈衝時間值換算成公分單位

Serial.print(d); // 顯示距離
Serial.print("cm");
Serial.println();

delay(1000); // 等待一秒鐘(每隔一秒量測一次)
}

修改後的練習

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const int pingPin = 7;
const int ledPin = 13;

void setup()
{
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}

void loop()
{
long duration, cm;
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin, LOW);
pinMode(pingPin, INPUT);
duration = pulseIn(pingPin, HIGH);
cm = microsecondestToCentimeters(duration);
Serial.print("Distance: ");
Serial.print(cm);
Serial.print("cm");
Serial.println();
if (cm < 100){
digitalWrite(ledPin, HIGH);
}
else {
digitalWrite(ledPin, LOW);
}
delay(100);
}
long microsecondestToCentimeters(long microseconds){
return microseconds / 29 / 2;
}
  • 依此配置,執行後,結果為
    test更改:原文圖片 09

電壓輸出

1
2
3
4
void setup(){
analogWrite(10,102);} // 指定10腳位輸出電壓為2v
void loop(){
}
  • 電路依此配置,執行後,結果為
    電壓輸出:原文圖片 10

變頻控制LED燈光和馬達


2019年11月18日

傳值、傳址、傳參考

  1. call by value(傳值):函式使用參數值的副本。
  2. call by address(傳址):以指標傳入位址,函式可透過指標操作原資料。
  3. call by reference(傳參考):以參考參數操作呼叫端的資料。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void swap(int* x,int* y){
int temp = *x;
*x = *y;
*y = temp;
}
void setup(){
int a=10, b=20;
Serial.begin(9600);
// 交換之前的a和b值
Serial.println(String("Before swap: ")+ "\t" + a + "\t" +b);
swap(&a,&b);
// 交換之後
Serial.println(String("After swap: ")+ "\t" + a + "\t" +b);
}
void loop(){
// 這裡沒有程式
}
  • 顯示如下:
    1
    2
    Before swap: 	10	20
    After swap: 20 10

8-1 函數

1
2
3
4
5
6
7
8
9
10
11
12
coid cirArea(){  // cirArea 為自定義函數名稱
int r=5;
float area = 3.14 *r *r;
Serial.println(area);
}
void setup(){
Serial.begin(9600);
}
void loop(){
cirArea(); //執行函數
delay(2000);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 動手做8-1:建立自訂函數
// 詳細的程式說明,請參閱第八章,8-5頁。

float ans; // 接收運算結果的變數

float cirArea(int r) {
float area = 3.14 * r * r;
return area;
}

void setup() {
Serial.begin(9600);
ans = cirArea(10); // 計算半徑 10 的圓面積
Serial.println(ans); // 顯示結果
ans = cirArea(20); // 計算半徑 20 的圓面積
Serial.println(ans); // 顯示結果
}
void loop() {
}
  • 執行結果如下
    1
    2
    314.00
    1256.00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
double result = 0;
double i;
double SumCC()
{
double SumC=0;
for (int n=1; n<101;n++){
SumC=(n*5/3)+SumC;
}
return SumC;
}
void setup()
{
Serial.begin(9600);
for( i=0 ; i<300 ; i++){
result=SumCC()+result;
}
Serial.println(result);
}

void loop(){

}

2019年12月02日

2019年12月02日:原文圖片 11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
double fun(double t)
{
double A=2;
double f=60;
double result;
double pi=3.1415926;

result = A*sin(2*pi*f*t);

return result;
}

void setup()
{
Serial.begin(9600);
for(double t=0;t<=(1);t=t+0.001)
{
double result = fun(t);
Serial.println(result);
}
}

void loop()
{
}
  • 執行後,結果為
    2019年12月02日:原文圖片 12

t = 0 度 ~ 360 度
x = A sin ( 2 π f t ) + B cos ( 2 π f t )
A = 0.2
B = 0.3
f = 60

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
double fun(double t)
{
double A=0.2;
double B=0.3;
double f=60;
double result;
double pi=3.1415926;

result = A*sin(2*pi*f*t) + B*cos(2*pi*f*t);

return result;
}

void setup()
{
Serial.begin(9600); // 初始化序列埠,以9600bps速率連線
for(double t=0;t<=(1);t=t+0.001)
{
double result = fun(t);
Serial.println(result);
}
}

void loop()
{
}
  • 執行後,結果為
    2019年12月02日:原文圖片 13

老師上課補充

中斷可在觸發條件發生時執行對應的服務函式,暫時處理事件後再回到原流程。它不是所有情況都具有「最高權限」的意思,實際行為依晶片與設定而定。

中斷

  • 線路配置圖
    中斷:原文圖片 14

程式內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int pbInt = 0; // 定義中斷引腳為0,也就是D2引腳
int ledOut = A0; // 定義輸出指示燈引腳
volatile int state = LOW; // 定義默認輸入狀態

void setup()
{
pinMode(ledOut, OUTPUT); // 置ledOut引腳為輸出狀態
attachInterrupt(pbInt, stateChange, CHANGE); // 監視中斷輸入引腳的變化
}
void loop()
{
for (int i = 0; i < 100; i++)
{
delay(100); // 什麼都不做,等待10毫秒
} // 模擬長時間運行的進程或複雜的任務
}

void stateChange()
{
state = !state;
digitalWrite(ledOut, state);
}

2019年12月09日

斜率

  1. x^2+5
1

2019年12月16日

外部中斷(FALLING)

FALLING 表示訊號由高轉低時觸發;CHANGE 表示訊號電位改變時觸發。可使用的中斷腳位與模式依板子而異,請先查閱對應規格。

線路布線圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
ShakeSwitch
中斷檢測振動感測器,控制LED燈亮滅
*/
int SensorLED = 9; //定義LED為數字引腳9
int SensorINPUT = 2; //連線震動開關到中斷0,也就是數字引腳2
char state = LOW;

void setup() {

pinMode(SensorLED, OUTPUT); //LED為輸出模式
pinMode(SensorINPUT, INPUT_PULLUP); //震動開關為輸入模式

//下降沿觸發,觸發中斷0,呼叫blink函式
attachInterrupt(0, blink, FALLING);
}

void loop() {

if (state == HIGH) { // 如果state為HIGH
state = LOW;
digitalWrite(SensorLED, HIGH); // 亮燈
delay(500); //延時500ms
}
else {
digitalWrite(SensorLED, LOW); // 否則,關燈
}
}
void blink() { //中斷函式blink()
state = !state; //一旦中斷觸發,state狀態反轉
}

進階(CHANGE)

線路布線圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
ShakeSwitch
中斷檢測振動感測器,控制LED燈亮滅
*/
int SensorLED = 9; //定義LED為數字引腳9
int SensorINPUT = 2; //連線震動開關到中斷0,也就是數字引腳2
char state = LOW;

void setup() {

pinMode(SensorLED, OUTPUT); //LED為輸出模式
pinMode(SensorINPUT, INPUT_PULLUP); //震動開關為輸入模式

//下降沿觸發,觸發中斷0,呼叫blink函式
attachInterrupt(0, blink, CHANGE);
}

void loop() {

if (state == HIGH) { // 如果state為HIGH
state = LOW;
digitalWrite(SensorLED, HIGH); // 亮燈
delay(500); //延時500ms
}
else {
digitalWrite(SensorLED, LOW); // 否則,關燈
}
}
void blink() { //中斷函式blink()
state = !state; //一旦中斷觸發,state狀態反轉
}

模擬與實體實作要分開確認

Tinkercad 畫面可協助觀察電路與程式,但實體板子仍需核對供電、腳位、元件規格及接線。修改時先測試單一感測器或輸出,再整合;中斷服務函式應保持簡短,避免在其中安排長時間等待。

參考:Arduino 官方語言參考。