2021年7月21日 星期三

ESP32 的第一課

ESP32 晶片內部有兩個處理器:「32 位元雙核心處理器」和「8 位元低功耗處理器(簡稱:ULP)」。其中 ESP32 主處理器的兩個核心分別叫做 PRO_CPU 和 APP_CPU:

  • PRO_CPU:代表 Protocol CPU,負責處理和操控 WiFi、藍芽和其他介面(I2C、SPI...等)。
  • APP_CPU:代表 Application CPU,執行主程式碼。

規格比較

型號ESP32ESP8266
處理器Xtensa 雙核心或單核心 32 位元 LX6Xtensa 單核心 32 位元 L106
工作時脈160/240 MHz80 MHz
協同處理器ULP無
SRAM 主記憶體520 KB120 KB
外部 Flash 快閃記憶體最高支援 16MB最高支援 16 MB
RTC(即時鐘)記憶體16 KB無
WiFi2.4 GHz 頻段的 802.11 b/g/n2.4 GHz 頻段的 802.11 b/g/n
藍芽4.2 版 BR/EDR/BLE無
乙太網路10/100 Mbps無
CAN 介面 ①2.0無
GPIO3416
UART 序列埠31.5(其中一組僅有傳送腳)
I2C 介面21
I2S 介面22
SPI 介面42
ADC(類比轉數位)18 個(12 位元),最大輸入電壓 3.6V1 個(10 位元),最大輸入電壓 1.1V
DAC(數位轉類比)2個(8 位元)2個(8 位元)
PWM 輸出168
觸控介面10無
溫度感測器有,用於測量晶片溫度無
霍爾感測器有,用於檢測磁力變化無
安全性安全啟動 Flash 加密 1024 OTP無
加密(crypto)AES、SHA-2、RSA、ECC、ENG無
功耗深度睡眠 10 uA深度睡眠 2 uA
註①:CAN 是 Controller Area Network(控制器區域網路)通訊協定的簡稱,基本用於工廠、汽車、雜
   訊較多的場所。

ESP32 圖例

NodeMCU-32S

NodeMCU-32S Pins MAP

ESP32的功能接腳與限制

  • Serial:
    硬體接腳 UART 0:TX(GPIO1)、RX(GPIO3)
    硬體接腳 UART 1:TX(GPIO9)、RX(GPIO10) ②
    硬體接腳 UART 2:TX(GPIO17)、RX(GPIO16)
    軟體模擬接腳:事實上,ESP32 軟體模擬序列埠,可以使用在任意接腳,其宣告語法為「Seria2.begin(9600,SERIAL_8N1,18,19)」,其中:
       Serial2:類別變數名
       9600:鮑率
       SERIAL_8N1:代表沒有同位檢查
       18:GPIO18 為 RX
       19:GPIO19 為 TX
  • I2C:
    SDA:GPIO22
    SCL:GPIO21
  • SPI:
    CLK:GPIO14
    MISO:GPIO12
    MOSI:GPIO13
    SS:GPIO15
  • VSPI:
    SCK:GPIO18
    MISO:GPIO19
    MOSI:GPIO23
    SS:GPIO5
  • 接腳的限制:
    (1)接腳的最大輸入電壓為 3.6V
    (2)不支援上拉電阻(INPUT_PULLUP)的接腳:32、35、34、39、36
    (3)不支援下拉電阻(INPUT_PULLDOWN)的接腳:0、1、2、3
    (4)接腳 34、35、36、39 只支援輸入
    (5)接腳 0、5、14、15 不建議設為 INPUT
    (6)接腳 1、3、12 不建議設為 OUTPUT
    (7)接腳 CLK、SD0、SD1、SD3、CMD 用於上傳程式至晶片,不能使用
    (8)接腳定額的電流為 6mA,最大電流為 12mA
註②:ESP32 的 GPIO 接腳 6、7、8、9、10、11 都連接到快閃記憶體,所以 Serial1 的
   RX(GPIO9)、TX(GPIO10)無法使用程式控制,是專門用來與記憶體溝通的接腳

ESP32 的 printf() 格式化輸出

雖然 Arduino 不支援 printf(),但基於 ESP-IDF 工具的 ESP32 跟 C 語言一樣,可以支援這個萬用的格式化輸出工具。

符號說明
%c單一字元(char)
%s字串(string)
%u無正負號(unsigned)整數
%d整數(integer)
%lu無正負長整數(unsigned long)
%ld長整數(long)
%x16 進位(hexadecimal)整數
%f浮點數(float),輸出全部整數部分,6 位小數部分,超過 6 位則四捨五入
%.nf輸出小數點後 n 位數字的浮點數,請留意 n 前面有個點「.」。例如:3.14159 用 %.3f 輸出為 3.142
%%顯示 %

數位輸出/輸入

接觸新的開發板時,最先要留意的是它的工作電壓規格。

  • ESP32 開發版的工作電壓(VDD)是 3.3V。
  • 數位及類比腳的最大容許輸入電壓是 VDD+0.3V,也就是 3.6V。
  • pinMode(pin,mode);

    mode 可能的值:

    • INPUT:當接腳設成「數位輸入」時,部分接腳會因以下的事件而不建議使用。
      (1) TX(GPIO1)連接 USB 埠,開機時會輸出除錯訊息。
      (3) RX(GPIO1)連接 USB 埠,開機時會處於高電位狀態。
      (12) GPIO1 開機時須維持在低電為,否則不能開機。
    • OUTPUT:當接腳設成「數位輸出」時,部分接腳會因以下的事件而不建議使用。
      (1) GPIO0、GPIO5、GPIO14、GPIO15 開機時會輸出短暫 PWM 訊號,不建議使用。
      (1) GPIO1、GPIO3 連接 USB 埠。
      (1) GPIO34、GPIO35、GPIO36、GPIO39 僅具備數位輸入功能。
    • INPUT_PULLUP:GPIO32~GPIO39 不具備上拉電阻之功能。
    • INPUT_PULLDOWN:GPIO0~GPIO3 不具備下拉電阻之功能。

    讀取電容觸控接腳

    ESP32 有 10 個接腳具備電容觸控感應功能,可以當除觸控開關,觸控腳的感測值是類比值,會因接線形式(觸控面積大小)、距離等因素而不同。讀取觸控感測值的語法為:

    int touchValue(pin);

    可使用的觸控接腳如下表:
    T0(GPIO4)T1(GPIO0)T2(GPIO2)T3(GPIO5)T4(GPIO13)
    T5(GPIO12)T6(GPIO14)T7(GPIO27)T8(GPIO33)T9(GPIO32)

    範例:底下程式將在使用者碰接腳 T3(GPIO15)時,會點亮開發版內建的 LED。

    #include LED LED_BUILTIN
    const byte touchPin = 15;
    const int thresHold = 20;
    int touchValue ;
    void setup()
    {
        pinMode(LED_BUILTIN, OUTPUT);
    }
    
    void loop()
    {
        touchValue = touchRead(touchPin);
    
        if(touchValue < thresHold)
        {
            digitalWrite(LED, LOW);
        }
        else
        {
            digitalWrite(LED, HIGH);
        }
        delay(1000);
    }

    程式的執行效果:

    ESP32 的類比輸入

    ESP32 類比輸入電壓的上限是 3.6V,正常最高值為 3.3V,讀取類比輸入值的指令同樣是 analogRead(),接腳可用 A0(GPIO36)、A3(GPIO39)、A4(GPIO32)、A5(GPIO33)、A6(GPIO34)、A7(GPIO35) 等編號。

    Arduino UNO 的類比數位轉換器(ADC)採 10 位元取藥,所以量化值介於 0~1023,ESP32 的 ADC 預設採 12 位元取樣,量化值介於 0~4095。

    設定 ESP32 的 ADC 類比輸入電壓範圍與取樣位元的方法:

    • analogSetAttenuation(衰減值):其中衰減值可能為
    • ADC_0db:0db 衰減,電壓輸入上限為 1.00V。
    • ADC_2_5db:2.5db 衰減,電壓輸入上限為 1.34V。
    • ADC_6db:6db 衰減,電壓輸入上限為 2.00V。
    • ADC_11db:11db 衰減,電壓輸入上限為 3.6V。
    • analogSetWidth(寬度):設定 ADC 的取樣位元
    • analogSetWidth(9):取樣寬度為 0~511。
    • analogSetWidth(10):取樣寬度為 0~1023。
    • analogSetWidth(12):取樣寬度為 0~4095。
    • analogRead(pin)::可用的類比輸入接腳為
    • A0:GPIO36,只有輸入功能。
    • A3:GPIO39,只有輸入功能。
    • A6:GPIO34,只有輸入功能。
    • A7:GPIO35,只有輸入功能。
    • A4:GPIO32。
    • A5:GPIO33。

    由於 ESP32 類比輸入的最高電壓是 3.6V,但如果類比感測器的輸出電壓是 5V,直接將感測器 5V 的輸出接到 ESP32 的類比輸入接腳,很可能會造成 ESP32 的損壞,所以我們需要將 5V 轉為 3V3。

    透過右圖的接線方式,我們可以順利達成這個目標。

    ESP32 內建的霍爾感應器

    ESP32 晶片內建一個可以偵測磁場變化的「霍爾效應③」感測器,霍爾感測器的輸出電壓和磁場的強度與距離成正比。

    霍爾感測器普遍應用在檢測物體移動、馬達轉速、開闔等。例如:無刷馬達的轉子包覆著磁石,在馬達內部裝設霍爾感測器,透過偵測磁石南北極的變化便可以測量馬達的轉速。在手機、平板電腦內部裝設霍爾感測器,同時在保護蓋說這一個磁石,便可以達成偵測保護蓋開闔的動作。

    註③:霍爾效應(Hall effect)是指當固體導體放置在一個磁場內,且有電流通過時,導體內的電荷載子
       會因受力而偏向一邊,繼而產生電壓。

    讀取霍爾感測值的函式叫做 hallRead(),它將傳回帶正負號的整數值。

    ESP32 的類比輸出

    ESP32 的 PWM 解析度不是固定值,而是跟頻率設定息息相關,根據樂鑫公司的 ESP32 官方技術文件(http://bit.ly/39wGPa5)指出:PWM 解析度隨著頻率提高而降低,解析度範圍介於 1~16 位元,頻率上限為 40MHz,解析度的計算公式請參考下圖。

    設定 ESP32 的 PWM 輸出指令

    設置 ESP32 的 PWM 輸出、頻率,須透過兩個函式來達成,韓式名字開頭的 ledc 代表 LED Control(LED 控制,簡稱 LEDC)。

    • ledcSetup(通道,頻率,解析度):設置 PWM 產生器
    • ledcAttachPin(接腳編號,通道)
      通道相當於 PWM 產生器,ESP32 內部共有 16 個 PWM 通道(0~15),每個通道的輸出都可設定給任一個數位輸出接腳。所以 ESP32 最多同時擁有 16 個 PWM 輸出腳。PWM 設定完畢,就可以執行 ledcWrite() 輸出 PWM 訊號。
    • ledcWrite(通道,PWM 輸出):參閱以下範例
      ledcSetup(0,5000,10); //--- 為通道 0 設定一個 5KHz、10 位元取樣頻率的 PWM 產生器
      ledcAttachPin(5,10); //--- 把 GPIO5 指定給通道 0 使用
      ledcWrite(0,614); //--- 在通道 0 輸出 614 的 PWM 訊號

    利用 PWM 輸出完成調光器

    實驗電路:利用一個 10K 的便電阻連接在 A4 接腳,接線示範圖如下

    程式碼如下:

    #define BITS 10
    
    void setup()
    {
        pinMode(LED_BUILTIN,OUTPUT);
        analogSetAttenuation(ADC_11db);
        analogSetWidth(BITS);
    
        ledcSetup(0,5000,BITS);
        ledcAttachPin(LED_BUILTIN,0);
    }
    
    void loop()
    {
        int val=1023-analogRead(A4);
        ledcWrite(0,val);
    }

    調控 PWM 訊號的頻率發出聲音

    ESP32 不支援 Arduino 的 tone() 函式,但 Arduino 的 tone() 本質上是用 PWM 來調控輸出波形(聲音)的頻率,所以 ESP32 也同樣可以用 PWM 訊號來驅動發音元件。透過改變訊號的「工作週期」來調整輸出電壓,以達成在揚聲器上發出不同音高的聲音。

    在 ESP32 上改變 PWM 頻率的函式語法:

    • ledcWriteTone(通道,頻率);
    • ledcWriteNote(通道,音名常數,音階編號);

    音名、音階編號與頻率的對應表:

    音名音名常數音階編號
    012345678
    CNOTE_C163365131262523104620934186
    C#NOTE_Cs173569139277554110922174435
    DNOTE_D183773147294587117523494699
    D#NOTE_Eb193978156311622124524894978
    ENOTE_E214182165330659131926375274
    FNOTE_F224487175349698139727945588
    F#NOTE_Fs234693185370740148029605920
    GNOTE_G254998196392784156831366272
    G#NOTE_Gs2652104208415831166133226645
    ANOTE_A2855110220440880176035207040
    A#NOTE_Bb2958117233466932186437297459
    BNOTE_Bb3162123247493988197639517902

    下面的兩行程式碼都可以在通道 0 產生中央 C 音:

    ledcWriteTone(0,262); //--- 在通道 0 產生 262Hz 的頻率
    ledcWriteNote(0,NOTE_C,4)

    另,Arduino 有個停止音頻輸出的 noTone(); 函式,在 ESP32 中可用底下這行敘述達成:

    ledcWriteTone(0,0); //--- 把通道 0 的頻率設為 0

    其他 LEDC 控制函式:

    • ledcRead(通道):傳回指定通道的工作週期(佔空比)值。
    • ledcReadFreq(通道):傳回指定通道的頻率值(若佔空比為 0 則傳回 0)。
    • ledcDetachPin(接腳):解除指定接腳的 LEDC 功能,即取消 PWM 輸出。

    最後,我們用一段程式來展示控制 ESP32 讓蜂鳴器發出聲音救護車警示音,接線法請參閱下圖。

    #define BITS 10
    #define BUZZER 22 //--- 蜂鳴器接腳
    
    void setup()
    {
        ledcSetup(0,2000,BITS); //--- PWM 設定為:通道 0、頻率 20KHz、解析度 10 位元
        ledcAttachPin(BUZZER,0);
    }
    
    void loop()
    {
        ledcWriteNote(0,NOTE_E,5);
        delay(500);
        ledcWriteNote(0,NOTE_A,4);
        delay(500);
    }