文章程式碼顯示

2017年10月22日 星期日

《入門》寫程式Arduino教學 - 07 : 什麼是函數? 副程式?




前面我們不斷的提到 "函數" 這兩個字,例如 setup 函數以及 loop 函數。到底什麼是函數呢?

函數我們又可以稱為副程式,其作用簡單來說就是將程式碼進行包裹達到簡化版面以及重複使用的目的,並且可以提升程式碼整體的"可讀性",在程式龐大的情況下特別有用。舉例來說當我們呼叫 setup 函數時,事實上 Arduino 在背地裡會做一些設定,但為了讓大家方便使用,這些設定會被包裹起來並且藏在底層裡,一般來說我們不需要額外的去做修改,在現階段大家也當已知就可以了。

為了讓大家更了解函數的用處,我們乾脆直接寫一個自己的函數來進行了解

將前一章 LED 點亮以及熄滅( 延遲200ms ) 的程式碼進行修改,原本的程式碼長成這個樣子

void setup(){
  pinMode(13,OUTPUT);  
}

void loop(){
  digitalWrite(13,LOW);
  delay(200);
  digitalWrite(13,HIGH);
  delay(200);
}

複習一下整個作動過程,首先開發版通電後進入 setup 初始化函數,配置腳位 13 為輸出模式。接著進入 loop 函數進行迴圈(循環執行) 點亮 LED 燈、延遲200ms、熄滅 LED 燈 、延遲200ms ... 循環執行的程式碼。

我們想將第 6 及 7 行的程式碼包裹成函數(副程式),並將此函數命名為 LED_ON 為己所用

void setup(){
  pinMode(13,OUTPUT);  
}

void loop(){
  LED_ON();
  digitalWrite(13,LOW);
  delay(200);
}

void LED_ON(){
  digitalWrite(13,HIGH);
  delay(200);
}

第 11 行的 void LED_ON() 就是在建立一個名為 LED_ON 的函數,並且函數內的程式碼為點亮 LED 燈、延遲200ms ( 原本的 6 及 7 行改放到 12 及 13行 包裹起來)。

如此一來當 Arduino 通電後,會先進入 setup 函數進行腳位配置,然後一樣進入 loop 迴圈。當我們執行到第 6 行時候,程式碼驚覺這是一條"函數呼叫"的指令(函數呼叫的寫法為函數名()加上一個分號),於是跳到第 11 行執行函數內的程式碼(也就是第 12 行及第 13 行)。結束後再跳回第 7 行,接著繼續執行第 8 行然後回到第 6 行反覆執行。

此處廣義的打破了 C 語言一開始所說的 "循序執行" ,讓程式碼有跳躍執行的情形發生,但跳躍後幹完事了還是會跳回來繼續循序執行的基本概念來運作。

需要注意的是函數在命名時最好是取一些有文意的名字以增加可讀性,且在命名時不得以數字為開頭,亦不可與關鍵字強碰(例如你不能命名為digitalWrite,setup ,loop 因為那個已經被內定使用了),當命名強碰到關鍵字時,會發現有變色的狀況發生,此時就知道該命名與關鍵字強碰了。

一開始使用時可能會認為,原本的目的不是為了讓整體更整潔、可讀性越高嗎? 怎麼感覺版面變得更複雜了 ?

事實上,我們之前用過的 digitalWrite 、 pinMode 以及 delay 都是函數呼叫,但屬於 "內建"的函數呼叫,也就是說上述的三個指令也有做類似 LED_ON 這種建立函數的過程,那為什麼我們在程式碼中沒有看到?

這是因為 Arduino IDE 將這些常用的函數,內部程式碼都藏了起來

例如 digitalWrite 函數在 D:\Arduino\hardware\arduino\avr\cores\arduino\wiring_digital.c 裡可以找到

void digitalWrite(uint8_t pin, uint8_t val)
{
 uint8_t timer = digitalPinToTimer(pin);
 uint8_t bit = digitalPinToBitMask(pin);
 uint8_t port = digitalPinToPort(pin);
 volatile uint8_t *out;

 if (port == NOT_A_PIN) return;

 // If the pin that support PWM output, we need to turn it off
 // before doing a digital write.
 if (timer != NOT_ON_TIMER) turnOffPWM(timer);

 out = portOutputRegister(port);

 uint8_t oldSREG = SREG;
 cli();

 if (val == LOW) {
  *out &= ~bit;
 } else {
  *out |= bit;
 }

 SREG = oldSREG;
}

現在看不懂很正常,待學的越來越深入就有機會看懂了!目前只要知道有這麼一回事就好

這段程式的內部實質上就是在進行暫存器的設置,也就是說當我們使用 digitalWrite(13,HIGH) 的時候,事實上是呼叫了上述的程式碼。

由此例中很簡單的感覺到,我們只要撰寫一行 digitalWrite 就等同一口氣執行了 26 行程式碼,是不是簡潔許多了呢 ?

我們也可以將 LED_ON 這種自己撰寫的函數藏起來,但為了別搞複雜在這就先不提了

回到我們的 LED_ON(); 函數,注意在小括弧裡面我們沒有填入任何的東西,若有填入東西我們稱為參數(引數)輸入。例如 digitalWrite(13,HIGH); 我們就填入了兩個參數分別是 13 以及 HIGH。而本章所提到的函數建立尚不包含回傳以及輸入參數值 .. 等等更深入的應用,僅先以最簡單的版本來進行解說。

↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引