《超圖解ESP32深度實作》範例檔更新說明

ESP32 Arduino開發平台目前最新版是3.x,跟《超圖解ESP32深度實作》採用的1.x版相比,調整了某些語法,但整體的程式運作邏輯不變。在新的版本中,有些是API被刪除或改名,例如,diy1_1的「選定要控制的接腳」:

gpio_pad_select_gpio(BLINK_GPIO);

改成「重置控制接腳」:

gpio_reset_pin(BLINK_GPIO);

有些則是底層的程式庫不再自動引用某些標頭檔,我們編寫的程式要自行引用。例如,第15章的一個BLE(低功耗藍牙)範例程式,透過esp_bt_dev_get_address()取得裝置的位址,在3.x版開發平台,程式開頭要明確引用esp_bt_device.h標頭檔,否則會發生編譯錯誤:

#include <esp_bt_device.h>  // 內含esp_bt_dev_get_address()的宣告 

此外,之前版本允許用底下的敘述直接定義C++的string(字串)型態變數:

using namespace std;
string s = "hello world!"; 

在3.x版開發平台,必須引用string程式庫:

#include <string>   // 必須明確引用string
using namespace std;
string s = "hello world!";

第15章的藍牙BLE程式中,接收並暫存「特徵」值的敘述:

std::string rxVal =  pCharact->getValue();

其變數要改成Arduino的String型態:

String rxVal = pCharact->getValue();

第16章的「鍵盤滑鼠組的程式庫(ESP32-BLE-Keyboard-Mouse)」,取得藍牙裝置名稱的deviceName屬性值:

BLEDevice::init(BleKBMouseObj->deviceName);

也要轉換成Arduino的String型態:

BLEDevice::init(String(BleKBMouseObj->deviceName.c_str()));

第19章使用的支援SSL/TSL加密的網站伺服器程式庫esp32_https_server,尚未更新,因此仍需採用2.x版開發平台編譯。

讀取ESP32內部霍爾感測器的功能則被刪除,所以底下這個API不存在了,要使用這項功能,請安裝2.x版的開發環境。

hallRread()  // 讀取霍爾感測值 

部份用到霍爾感測器的範例,例如第15章的BLE(低功耗藍牙)單元,改用讀取A0腳,讀者可在A0腳改接可變電阻代替,當然,也可以連接霍爾感測器模組(日後說明)。

#if ESP_ARDUINO_VERSION >=  ESP_ARDUINO_VERSION_VAL(3, 0, 0)
   int hallVal = analogRead(A0);  // 讀取A0腳 
#else
   int hallVal = hallRead();  // 讀取霍爾感測器值 
#endif

調整程式記憶體分區的大小

有些程式庫有更新支援3.x版開發平台,例如,驅動I2S裝置輸出立體聲的ESP32-A2DP,請下載、安裝新版程式庫。然而,使用新版ESP32-A2DP程式庫,搭配WEMOS D1 MINI ESP32開發板,編譯動手做14-1的原始碼,出現編譯錯誤 “text section exceeds available space in board(文字部分超出開發板可用空間)”。

編譯檔過大錯誤

底下是編譯器的部分輸出訊息:

Sketch uses 1430869 bytes (109%) of  program storage space. Maximum is 1310720 bytes.

草稿碼使用了 1430869 位元組 (109%) 的程式儲存空間。上限為 1310720 位元組。

Global variables use 54008 bytes (16%)  of dynamic memory, leaving 273672 bytes for local variables. Maximum is 327680  bytes.

全域變數使用了 54008 位元組 (16%) 的動態記憶體,剩餘 273672 位元組用於區域變數。上限為 327680 位元組。

解決辦法是加大程式記憶體分區。WEMOS D1 MINI ESP32開發板的Partition Scheme(分區方案)選項的預設值是“Default(預設)”,改成“No OTA (Large APP)”,代表沒有OTA(Over-The-Air,無線更新韌體)分區。

選擇程式記憶體分區

重新編譯就過關了,編譯訊息如下:

Sketch uses 1430869 bytes (68%) of  program storage space. Maximum is 2097152 bytes.

草稿碼使用 1430869 位元組 (68%) 的程式儲存空間。上限為 2097152 位元組。

計時器中斷

書本4-23頁的「計時器單元」程式,採用Timer(計時器)物件和中斷處理常式建立「每隔一段時間,執行某段敘述」的程式。Timer物件在3.x版開發平台也做了些許調整,樂鑫官網的Timer(計時器)文件,列舉了Timer API的全部函式與說明。本文只用到下列四個函式:

  • timerBegin():設定並啟用計時器,用手機APP來比喻,此舉相當於「打開計時器APP」。
  • timerAttachInterrupt():附加中斷處理常式給計時器物件
  • timerDetachInterrupt():解除附加在計時器物件的中斷處理常式
  • timerAlarm():設定計時器的觸發時間和次數,相當於在手機上設定計時器APP的鬧鈴時間。
  • timerEnd():結束計時器,相當於在手機上關閉計時器APP。

新版Timer API的運作流程,大致跟書本上採用的舊版API相同:宣告計時器物件、定義計時器中斷處理常式,但初始化和啟用計時器的流程從呼叫4個函式簡化成3個,原本偵測計時器時脈上下緣(edge)的參數也取消了。

新版Timer API的運作流程

timerAlarm()最後的「計數值(counter)」參數,用於設定每次計時器觸發後的計數器初始值,通常設定成0。這個計數值存位計時器物件內部,可透過timerRead(讀取計數值)、timerReadMicros(讀取轉成微秒後的計數值)以及timerReadMillis(讀取轉成毫秒後的計數值)等函式取得。

假設計時器物件叫做timer,中斷處理常式叫做onTimer,底下三個程式片段將分別令計時器每隔1秒、0.5秒和10秒,反覆觸發:

新版Timer API的運作流程

底下是一個簡易的定時器程式範例,令開發板內建的LED每0.5秒交替閃爍。

volatile bool toggle = 0;   // 紀錄內建LED的狀態

portMUX_TYPE tmux = portMUX_INITIALIZER_UNLOCKED; // 資源鎖

hw_timer_t * timer;         // 計時器物件

void IRAM_ATTR onTimer() {  // 計時器中斷處理常式
  portENTER_CRITICAL(&tmux);
  toggle = !toggle;
  digitalWrite(LED_BUILTIN, toggle);
  portEXIT_CRITICAL(&tmux);
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  timer = timerBegin(1e6);  // 初始化計時器,頻率:1Mhz
  timerAttachInterrupt(timer, &onTimer); // 附加onTimer給計時器
  timerAlarm(timer, 5e5, true, 0);
}

void loop() { }

底下是修改書本「動手做4-4」,支援3.x版開發平台的範例:

#define LED0 2
#define LED1 19        // 外接LED的接腳

volatile bool state0 = 0;   // 紀錄內建LED的狀態
volatile bool state1 = 0;   // 紀錄外接LED的狀態
volatile byte counter = 0;  // 計數器

portMUX_TYPE mux0 = portMUX_INITIALIZER_UNLOCKED; // 資源鎖0
portMUX_TYPE mux1 = portMUX_INITIALIZER_UNLOCKED; // 資源鎖1

hw_timer_t * timer0;   // 計時器物件0
hw_timer_t * timer1;

void IRAM_ATTR onTimer0() {
  portENTER_CRITICAL(&mux0);
  state0 = !state0;
  digitalWrite(LED0, state0);
  portEXIT_CRITICAL(&mux0);
}

void IRAM_ATTR onTimer1() {
  portENTER_CRITICAL(&mux1);
  state1 = !state1;
  digitalWrite(LED1, state1);
  if (++counter == 10) {
    if (timer1 != NULL) {
    #if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(3, 0, 0)
      timerAlarmDisable(timer1);  // 用於2.x版
    #endif
      timerDetachInterrupt(timer1);
      timerEnd(timer1);
      timer1 = NULL;
    }
  }
  portEXIT_CRITICAL(&mux1);
}

void setup() {
  pinMode(LED0, OUTPUT);
  pinMode(LED1, OUTPUT);

#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
  timer0 = timerBegin(1e6);  // 1e6 = 1000000,1MHz
  timer1 = timerBegin(1e6);

  timerAttachInterrupt(timer0, &onTimer0);	
  timerAttachInterrupt(timer1, &onTimer1);

  timerAlarm(timer0, 1e6, true, 0);  // 1000ms
  timerAlarm(timer1, 5e5, true, 0);  // 500ms
#else
  timer0 = timerBegin(0, 80, true);  // 設置計時器0
  timer1 = timerBegin(1, 80, true);  // 設置計時器1

  timerAttachInterrupt(timer0, &onTimer0, true);
  timerAttachInterrupt(timer1, &onTimer1, true);
  
  timerAlarmWrite(timer0, 1000000, true);   // 1000ms(1秒)
  timerAlarmWrite(timer1, 500000, true);    // 500ms(0.5秒)

  timerAlarmEnable(timer0);  // 啟動計時器
  timerAlarmEnable(timer1);
#endif
}

void loop() { }

《超圖解ESP32深度實作》範例檔已全數更新,請讀者重新下載。有關使用ESP_ARDUINO_VERSION_VAL()巨集區分ESP32 Arduino開發平台、Arduino IDE 2.x版的操作簡介在IDE 2.x上傳資料檔到ESP32 SPIFFS分區,以及PWM輸出的語法更新說明,請參閱「《超圖解ESP32應用實作》範例檔更新支援開發環境3.x版及Arduino IDE 2.x資料上傳工具操作說明」貼文。

Posts created 487

發佈留言

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

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top